From 79a83a7b8c877aa8d71ad63b00bbee4606a93dd1 Mon Sep 17 00:00:00 2001 From: Mattias Reichel Date: Thu, 13 Nov 2025 11:09:36 +0100 Subject: [PATCH 1/2] feat!: Grails 7 --- .github/release-drafter.yml | 134 +++ .github/workflows/gradle.yml | 60 + .github/workflows/release.yml | 115 ++ .gitignore | 1 + .sdkmanrc | 1 + .travis.yml | 18 - LICENSE | 2 +- README.md | 22 +- build.gradle | 96 +- buildSrc/build.gradle | 69 ++ .../main/groovy/build/config/CodeNarc.groovy | 57 + .../src/main/groovy/build/config/Docs.groovy | 92 ++ .../main/groovy/build/config/Grails.groovy | 20 + .../src/main/groovy/build/config/Java.groovy | 30 + .../main/groovy/build/config/Publish.groovy | 29 + .../groovy/build/config/Reproducible.groovy | 31 + config/codenarc/codenarc.groovy | 1 - examples/app1/build.gradle | 77 ++ .../assets/images/advancedgrails.svg | 27 + .../assets/images/apple-touch-icon-retina.png | Bin 0 -> 7038 bytes .../assets/images/apple-touch-icon.png | Bin 0 -> 3077 bytes .../assets/images/documentation.svg | 19 + .../app1/grails-app/assets/images/favicon.ico | Bin 0 -> 5558 bytes .../images/grails-cupsonly-logo-white.svg | 26 + .../app1/grails-app/assets/images/grails.svg | 13 + .../assets/images/skin/database_add.png | Bin 0 -> 658 bytes .../assets/images/skin/database_delete.png | Bin 0 -> 659 bytes .../assets/images/skin/database_edit.png | Bin 0 -> 767 bytes .../assets/images/skin/database_save.png | Bin 0 -> 755 bytes .../assets/images/skin/database_table.png | Bin 0 -> 726 bytes .../assets/images/skin/exclamation.png | Bin 0 -> 701 bytes .../grails-app/assets/images/skin/house.png | Bin 0 -> 806 bytes .../assets/images/skin/information.png | Bin 0 -> 778 bytes .../assets/images/skin/sorted_asc.gif | Bin 0 -> 835 bytes .../assets/images/skin/sorted_desc.gif | Bin 0 -> 834 bytes .../app1/grails-app/assets/images/slack.svg | 18 + .../app1/grails-app/assets/images/spinner.gif | Bin 0 -> 2037 bytes .../assets/javascripts/application.js | 29 + .../assets/stylesheets/application.css | 34 + .../grails-app/assets/stylesheets/errors.css | 117 ++ .../grails-app/assets/stylesheets/grails.css | 1071 +++++++++++++++++ .../grails-app/assets/stylesheets/main.css | 608 ++++++++++ .../grails-app/assets/stylesheets/mobile.css | 101 ++ examples/app1/grails-app/conf/application.yml | 30 + .../app1/grails-app/conf/logback-spring.xml | 33 + .../grails-app/conf/spring/resources.groovy | 3 + .../controllers/app/UrlMappings.groovy | 16 + .../app1/grails-app/domain/app}/Person.groovy | 12 +- .../grails-app/init/app}/Application.groovy | 11 +- .../services/app/PeopleDataService.groovy | 18 + .../services/app/PeopleService.groovy | 39 + .../app1/grails-app}/views/error.gsp | 0 examples/app1/grails-app/views/index.gsp | 78 ++ .../app1/grails-app/views/layouts/main.gsp | 73 ++ .../app1/grails-app}/views/notFound.gsp | 0 .../delete/AnnotatedDataServiceSpec.groovy | 46 + .../grails/logical/delete/CriteriaSpec.groovy | 73 ++ .../delete/DetachedCriteriaSpec.groovy | 71 ++ .../logical/delete/DynamicFindersSpec.groovy | 95 ++ .../logical/delete/LogicalDeleteSpec.groovy | 168 +++ .../logical/delete/WithDeletedSpec.groovy | 137 +++ .../WithDeletedTransformationSpec.groovy | 62 + .../logical/delete/test/PersonTestData.groovy | 18 + gradle.properties | 16 +- gradle/wrapper/gradle-wrapper.jar | Bin 52818 -> 43764 bytes gradle/wrapper/gradle-wrapper.properties | 5 +- gradlew | 309 +++-- gradlew.bat | 90 +- grails-app/conf/application.yml | 104 -- grails-app/conf/logback.groovy | 37 - .../init/gorm/logical/delete/BootStrap.groovy | 9 - grails-app/views/index.gsp | 77 -- grails-app/views/layouts/main.gsp | 50 - grails-wrapper.jar | Bin 5463 -> 0 bytes grailsw | 152 --- grailsw.bat | 89 -- plugin/build.gradle | 45 + plugin/src/docs/index.adoc | 161 +++ .../logical/delete/LogicalDelete.groovy | 232 ++++ .../LogicalDeleteAutoConfiguration.groovy | 39 + .../delete/LogicalDeleteGrailsPlugin.groovy | 42 + .../logical/delete/PreQueryListener.groovy | 31 +- .../delete/annotations/WithDeleted.groovy | 8 +- .../ast/WithDeletedTransformation.groovy | 159 +++ ...ot.autoconfigure.AutoConfiguration.imports | 1 + settings.gradle | 7 + src/docs/asciidoc/deletingObjects.ad | 23 - src/docs/asciidoc/implementation.ad | 15 - src/docs/asciidoc/index.ad | 36 - src/docs/asciidoc/installation.ad | 10 - src/docs/asciidoc/introduction.ad | 7 - src/docs/asciidoc/logicalDeleteTrait.ad | 10 - src/docs/asciidoc/queries.ad | 54 - src/docs/asciidoc/withDeletedAnnotation.ad | 8 - .../GormLogicalDeleteGrailsPlugin.groovy | 54 - .../gorm/logical/delete/LogicalDelete.groovy | 118 -- .../ast/WithDeletedTransformation.groovy | 114 -- .../gorm/logical/delete/CriteriaSpec.groovy | 66 - .../delete/DetachedCriteriaSpec.groovy | 74 -- .../logical/delete/DynamicFindersSpec.groovy | 94 -- .../logical/delete/LogicalDeleteSpec.groovy | 258 ---- .../logical/delete/WithDeletedSpec.groovy | 115 -- ...erviceWithDeletedTransformationSpec.groovy | 60 - .../WithDeletedTransformationSpec.groovy | 53 - .../logical/delete/test/PersonTestData.groovy | 28 - travis-build.sh | 70 -- 106 files changed, 4753 insertions(+), 2078 deletions(-) create mode 100644 .github/release-drafter.yml create mode 100644 .github/workflows/gradle.yml create mode 100644 .github/workflows/release.yml create mode 100644 .sdkmanrc delete mode 100755 .travis.yml create mode 100644 buildSrc/build.gradle create mode 100644 buildSrc/src/main/groovy/build/config/CodeNarc.groovy create mode 100644 buildSrc/src/main/groovy/build/config/Docs.groovy create mode 100644 buildSrc/src/main/groovy/build/config/Grails.groovy create mode 100644 buildSrc/src/main/groovy/build/config/Java.groovy create mode 100644 buildSrc/src/main/groovy/build/config/Publish.groovy create mode 100644 buildSrc/src/main/groovy/build/config/Reproducible.groovy create mode 100644 examples/app1/build.gradle create mode 100644 examples/app1/grails-app/assets/images/advancedgrails.svg create mode 100644 examples/app1/grails-app/assets/images/apple-touch-icon-retina.png create mode 100644 examples/app1/grails-app/assets/images/apple-touch-icon.png create mode 100644 examples/app1/grails-app/assets/images/documentation.svg create mode 100644 examples/app1/grails-app/assets/images/favicon.ico create mode 100644 examples/app1/grails-app/assets/images/grails-cupsonly-logo-white.svg create mode 100644 examples/app1/grails-app/assets/images/grails.svg create mode 100644 examples/app1/grails-app/assets/images/skin/database_add.png create mode 100644 examples/app1/grails-app/assets/images/skin/database_delete.png create mode 100644 examples/app1/grails-app/assets/images/skin/database_edit.png create mode 100644 examples/app1/grails-app/assets/images/skin/database_save.png create mode 100644 examples/app1/grails-app/assets/images/skin/database_table.png create mode 100644 examples/app1/grails-app/assets/images/skin/exclamation.png create mode 100644 examples/app1/grails-app/assets/images/skin/house.png create mode 100644 examples/app1/grails-app/assets/images/skin/information.png create mode 100644 examples/app1/grails-app/assets/images/skin/sorted_asc.gif create mode 100644 examples/app1/grails-app/assets/images/skin/sorted_desc.gif create mode 100644 examples/app1/grails-app/assets/images/slack.svg create mode 100644 examples/app1/grails-app/assets/images/spinner.gif create mode 100644 examples/app1/grails-app/assets/javascripts/application.js create mode 100644 examples/app1/grails-app/assets/stylesheets/application.css create mode 100644 examples/app1/grails-app/assets/stylesheets/errors.css create mode 100644 examples/app1/grails-app/assets/stylesheets/grails.css create mode 100644 examples/app1/grails-app/assets/stylesheets/main.css create mode 100644 examples/app1/grails-app/assets/stylesheets/mobile.css create mode 100644 examples/app1/grails-app/conf/application.yml create mode 100644 examples/app1/grails-app/conf/logback-spring.xml create mode 100644 examples/app1/grails-app/conf/spring/resources.groovy create mode 100644 examples/app1/grails-app/controllers/app/UrlMappings.groovy rename {src/test/groovy/gorm/logical/delete/test => examples/app1/grails-app/domain/app}/Person.groovy (55%) rename {grails-app/init/gorm/logical/delete => examples/app1/grails-app/init/app}/Application.groovy (65%) create mode 100644 examples/app1/grails-app/services/app/PeopleDataService.groovy create mode 100644 examples/app1/grails-app/services/app/PeopleService.groovy rename {grails-app => examples/app1/grails-app}/views/error.gsp (100%) mode change 100755 => 100644 create mode 100644 examples/app1/grails-app/views/index.gsp create mode 100644 examples/app1/grails-app/views/layouts/main.gsp rename {grails-app => examples/app1/grails-app}/views/notFound.gsp (100%) mode change 100755 => 100644 create mode 100644 examples/app1/src/integration-test/groovy/grails/logical/delete/AnnotatedDataServiceSpec.groovy create mode 100644 examples/app1/src/integration-test/groovy/grails/logical/delete/CriteriaSpec.groovy create mode 100644 examples/app1/src/integration-test/groovy/grails/logical/delete/DetachedCriteriaSpec.groovy create mode 100644 examples/app1/src/integration-test/groovy/grails/logical/delete/DynamicFindersSpec.groovy create mode 100644 examples/app1/src/integration-test/groovy/grails/logical/delete/LogicalDeleteSpec.groovy create mode 100644 examples/app1/src/integration-test/groovy/grails/logical/delete/WithDeletedSpec.groovy create mode 100644 examples/app1/src/integration-test/groovy/grails/logical/delete/WithDeletedTransformationSpec.groovy create mode 100644 examples/app1/src/integration-test/groovy/grails/logical/delete/test/PersonTestData.groovy delete mode 100644 grails-app/conf/application.yml delete mode 100755 grails-app/conf/logback.groovy delete mode 100644 grails-app/init/gorm/logical/delete/BootStrap.groovy delete mode 100755 grails-app/views/index.gsp delete mode 100755 grails-app/views/layouts/main.gsp delete mode 100755 grails-wrapper.jar delete mode 100755 grailsw delete mode 100755 grailsw.bat create mode 100644 plugin/build.gradle create mode 100755 plugin/src/docs/index.adoc create mode 100644 plugin/src/main/groovy/grails/logical/delete/LogicalDelete.groovy create mode 100644 plugin/src/main/groovy/grails/logical/delete/LogicalDeleteAutoConfiguration.groovy create mode 100644 plugin/src/main/groovy/grails/logical/delete/LogicalDeleteGrailsPlugin.groovy rename {src/main/groovy/gorm => plugin/src/main/groovy/grails}/logical/delete/PreQueryListener.groovy (57%) rename {src/main/groovy/gorm => plugin/src/main/groovy/grails}/logical/delete/annotations/WithDeleted.groovy (77%) create mode 100644 plugin/src/main/groovy/grails/logical/delete/ast/WithDeletedTransformation.groovy create mode 100644 plugin/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports create mode 100644 settings.gradle delete mode 100644 src/docs/asciidoc/deletingObjects.ad delete mode 100644 src/docs/asciidoc/implementation.ad delete mode 100755 src/docs/asciidoc/index.ad delete mode 100755 src/docs/asciidoc/installation.ad delete mode 100755 src/docs/asciidoc/introduction.ad delete mode 100644 src/docs/asciidoc/logicalDeleteTrait.ad delete mode 100644 src/docs/asciidoc/queries.ad delete mode 100644 src/docs/asciidoc/withDeletedAnnotation.ad delete mode 100644 src/main/groovy/gorm/logical/delete/GormLogicalDeleteGrailsPlugin.groovy delete mode 100644 src/main/groovy/gorm/logical/delete/LogicalDelete.groovy delete mode 100644 src/main/groovy/org/gorm/logical/delete/ast/WithDeletedTransformation.groovy delete mode 100644 src/test/groovy/gorm/logical/delete/CriteriaSpec.groovy delete mode 100644 src/test/groovy/gorm/logical/delete/DetachedCriteriaSpec.groovy delete mode 100644 src/test/groovy/gorm/logical/delete/DynamicFindersSpec.groovy delete mode 100644 src/test/groovy/gorm/logical/delete/LogicalDeleteSpec.groovy delete mode 100644 src/test/groovy/gorm/logical/delete/WithDeletedSpec.groovy delete mode 100644 src/test/groovy/gorm/logical/delete/annotations/GormDataServiceWithDeletedTransformationSpec.groovy delete mode 100644 src/test/groovy/gorm/logical/delete/annotations/WithDeletedTransformationSpec.groovy delete mode 100644 src/test/groovy/gorm/logical/delete/test/PersonTestData.groovy delete mode 100755 travis-build.sh diff --git a/.github/release-drafter.yml b/.github/release-drafter.yml new file mode 100644 index 0000000..38a91ce --- /dev/null +++ b/.github/release-drafter.yml @@ -0,0 +1,134 @@ +name-template: $RESOLVED_VERSION +tag-template: v$RESOLVED_VERSION +pull-request: + title-templates: + fix: 'πŸ› $TITLE (#$NUMBER)' + feat: 'πŸš€ $TITLE (#$NUMBER)' + default: '$TITLE (#$NUMBER)' +autolabeler: + - label: 'bug' + branch: + - '/fix\/.+/' + title: + - '/fix/i' + - label: 'improvement' + branch: + - '/improv\/.+/' + title: + - '/improv/i' + - label: 'feature' + branch: + - '/feature\/.+/' + title: + - '/feat/i' + - label: 'documentation' + branch: + - '/docs\/.+/' + title: + - '/docs/i' + - label: 'maintenance' + branch: + - '/(chore|refactor|style|test|ci|perf|build)\/.+/' + title: + - '/(chore|refactor|style|test|ci|perf|build)/i' + - label: 'chore' + branch: + - '/chore\/.+/' + title: + - '/chore/i' + - label: 'refactor' + branch: + - '/refactor\/.+/' + title: + - '/refactor/i' + - label: 'style' + branch: + - '/style\/.+/' + title: + - '/style/i' + - label: 'test' + branch: + - '/test\/.+/' + title: + - '/test/i' + - label: 'ci' + branch: + - '/ci\/.+/' + title: + - '/ci/i' + - label: 'perf' + branch: + - '/perf\/.+/' + title: + - '/perf/i' + - label: 'build' + branch: + - '/build\/.+/' + title: + - '/build/i' + - label: 'deps' + branch: + - '/deps\/.+/' + title: + - '/deps/i' + - label: 'revert' + branch: + - '/revert\/.+/' + title: + - '/revert/i' +categories: + - title: 'πŸš€ Features' + labels: + - 'feature' + - "type: enhancement" + - "type: new feature" + - "type: major" + - "type: minor" + - title: 'πŸ’‘ Improvements' + labels: + - 'improvement' + - "type: improvement" + + - title: 'πŸ› Bug Fixes' + labels: + - 'fix' + - 'bug' + - "type: bug" + - title: 'πŸ“š Documentation' + labels: + - 'docs' + - title: 'πŸ”§ Maintenance' + labels: + - 'maintenance' + - 'chore' + - 'refactor' + - 'style' + - 'test' + - 'ci' + - 'perf' + - 'build' + - "type: ci" + - "type: build" + - title: 'βͺ Reverts' + labels: + - 'revert' +change-template: '- $TITLE @$AUTHOR (#$NUMBER)' +version-resolver: + major: + labels: + - 'type: major' + minor: + labels: + - 'type: minor' + patch: + labels: + - 'type: patch' + default: patch +template: | + ## What's Changed + + $CHANGES + + ## Contributors + + $CONTRIBUTORS \ No newline at end of file diff --git a/.github/workflows/gradle.yml b/.github/workflows/gradle.yml new file mode 100644 index 0000000..253cd54 --- /dev/null +++ b/.github/workflows/gradle.yml @@ -0,0 +1,60 @@ +name: "Java CI" +on: + push: + branches: + - '[0-9]+.[0-9]+.x' + pull_request: + branches: + - '[0-9]+.[0-9]+.x' + workflow_dispatch: +env: + GRAILS_PUBLISH_RELEASE: 'false' + JAVA_DISTRIBUTION: liberica + JAVA_VERSION: 17.0.17 +jobs: + test_project: + name: "Test Project" + if: github.event_name == 'pull_request' || github.event_name == 'workflow_dispatch' + runs-on: ubuntu-24.04 + steps: + - name: "πŸ“₯ Checkout repository" + uses: actions/checkout@v5 + - name: "β˜•οΈ Setup JDK" + uses: actions/setup-java@v4 + with: + java-version: ${{ env.JAVA_VERSION }} + distribution: ${{ env.JAVA_DISTRIBUTION }} + - name: "🐘 Setup Gradle" + uses: gradle/actions/setup-gradle@v5 + - name: "πŸƒβ€β™‚οΈ Run Tests" + run: ./gradlew check --continue + publish_snapshot: + name: "Build Project and Publish Snapshot" + runs-on: ubuntu-24.04 + if: (github.event_name == 'push' || github.event_name == 'workflow_dispatch') && github.repository_owner == 'grails-plugins' + permissions: + contents: write + steps: + - name: "πŸ“₯ Checkout repository" + uses: actions/checkout@v5 + - name: "β˜•οΈ Setup JDK" + uses: actions/setup-java@v5 + with: + java-version: ${{ env.JAVA_VERSION }} + distribution: ${{ env.JAVA_DISTRIBUTION }} + - name: "🐘 Setup Gradle" + uses: gradle/actions/setup-gradle@v5 + - name: "πŸ“€ Publish Snapshot artifacts" + env: + MAVEN_PUBLISH_USERNAME: ${{ secrets.MAVEN_PUBLISH_USERNAME }} + MAVEN_PUBLISH_PASSWORD: ${{ secrets.MAVEN_PUBLISH_PASSWORD }} + MAVEN_PUBLISH_URL: 'https://repo.grails.org/artifactory/plugins3-snapshots-local' + run: ./gradlew publish + - name: "πŸ”¨ Generate Snapshot Documentation" + run: ./gradlew docs + - name: "πŸš€ Publish to Github Pages" + uses: apache/grails-github-actions/deploy-github-pages@asf + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GRADLE_PUBLISH_RELEASE: 'false' + SOURCE_FOLDER: plugin/build/docs diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..c6ca6ca --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,115 @@ +name: Release +on: + release: + types: [ published ] +env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GRAILS_PUBLISH_RELEASE: 'true' + JAVA_DISTRIBUTION: liberica + JAVA_VERSION: 17.0.17 # this must be a specific version for reproducible builds, keep in sync with .sdkmanrc + REPO_NAME: ${{ github.event.repository.name }} + TAG: ${{ github.event.release.tag_name }} + VERSION: will be computed in each job +jobs: + publish: + name: "Create and Stage Release Artifacts" + permissions: + contents: write # to add distribution to the GitHub release page + issues: write # to modify milestones + runs-on: ubuntu-24.04 + steps: + - name: "πŸ“ Establish release version" + run: | + echo "Release version: ${TAG#v}" + echo "VERSION=${TAG#v}" >> "$GITHUB_ENV" + - name: "πŸ“₯ Checkout the repository" + uses: actions/checkout@v5 + with: + ref: ${{ env.TAG }} + token: ${{ secrets.GITHUB_TOKEN }} + - name: "πŸ“… Store common build date" # to ensure a reproducible build + run: echo "SOURCE_DATE_EPOCH=$(git log -1 --pretty=%ct)" >> "$GITHUB_ENV" + - name: "πŸ“… Ensure source files use common date" + run: find . -depth \( -type f -o -type d \) -exec touch -d "@${SOURCE_DATE_EPOCH}" {} + + - name: "β˜•οΈ Setup JDK" + uses: actions/setup-java@v5 + with: + distribution: ${{ env.JAVA_DISTRIBUTION }} + java-version: ${{ env.JAVA_VERSION }} + - name: "🐘 Setup Gradle" + uses: gradle/actions/setup-gradle@v5 + - name: "βš™οΈ Run pre-release" + uses: grails/github-actions/pre-release@asf + env: + RELEASE_VERSION: ${{ env.VERSION }} + - name: "πŸ” Generate key file for artifact signing" + env: + SECRING_FILE: ${{ secrets.SECRING_FILE }} + run: | + printf "%s" "$SECRING_FILE" | base64 -d > "${{ github.workspace }}/secring.gpg" + - name: "πŸ“€ Publish to Maven Central" + env: + NEXUS_PUBLISH_USERNAME: ${{ secrets.NEXUS_PUBLISH_USERNAME }} + NEXUS_PUBLISH_PASSWORD: ${{ secrets.NEXUS_PUBLISH_PASSWORD }} + NEXUS_PUBLISH_URL: 'https://ossrh-staging-api.central.sonatype.com/service/local/' + NEXUS_PUBLISH_DESCRIPTION: '${{ env.REPO_NAME }}:${{ env.VERSION }}' + SIGNING_KEY: ${{ secrets.SIGNING_KEY }} + SIGNING_PASSPHRASE: ${{ secrets.SIGNING_PASSPHRASE }} + run: > + ./gradlew + -Psigning.keyId=${SIGNING_KEY} + -Psigning.secretKeyRingFile=${{ github.workspace }}/secring.gpg + publishMavenPublicationToSonatypeRepository + closeSonatypeStagingRepository + - name: "πŸ“… Generate Build Date file" + run: echo "$SOURCE_DATE_EPOCH" >> build/BUILD_DATE.txt + - name: "πŸ“€ Upload Build Date file" + uses: softprops/action-gh-release@v2 + with: + tag_name: ${{ env.TAG }} + files: build/BUILD_DATE.txt + release: + name: "Release Staging Repository, Publish Docs and Run Post-Release" + needs: publish + runs-on: ubuntu-24.04 + permissions: + contents: write + issues: write + pull-requests: write + steps: + - name: "πŸ“ Establish release version" + run: | + echo "Release version: ${TAG#v}" + echo "VERSION=${TAG#v}" >> "$GITHUB_ENV" + - name: "πŸ“₯ Checkout repository" + uses: actions/checkout@v5 + with: + ref: ${{ env.TAG }} + token: ${{ secrets.GITHUB_TOKEN }} + - name: "β˜•οΈ Setup JDK" + uses: actions/setup-java@v5 + with: + distribution: ${{ env.JAVA_DISTRIBUTION }} + java-version: ${{ env.JAVA_VERSION }} + - name: "🐘 Setup Gradle" + uses: gradle/actions/setup-gradle@v5 + - name: "πŸ“€ Release staging repository" + env: + NEXUS_PUBLISH_USERNAME: ${{ secrets.NEXUS_PUBLISH_USERNAME }} + NEXUS_PUBLISH_PASSWORD: ${{ secrets.NEXUS_PUBLISH_PASSWORD }} + NEXUS_PUBLISH_URL: 'https://ossrh-staging-api.central.sonatype.com/service/local/' + NEXUS_PUBLISH_DESCRIPTION: '${{ env.REPO_NAME }}:${{ env.VERSION }}' + run: > + ./gradlew + findSonatypeStagingRepository + releaseSonatypeStagingRepository + - name: "πŸ“– Generate Documentation" + run: ./gradlew docs + - name: "πŸ“€ Publish Documentation to Github Pages" + uses: apache/grails-github-actions/deploy-github-pages@asf + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GRADLE_PUBLISH_RELEASE: 'true' + SOURCE_FOLDER: plugin/build/docs + - name: "βš™οΈ Run post-release" + uses: apache/grails-github-actions/post-release@asf diff --git a/.gitignore b/.gitignore index f33ace2..b9eab1c 100755 --- a/.gitignore +++ b/.gitignore @@ -10,3 +10,4 @@ out/ .project .settings .classpath +!buildSrc/src/main/groovy/build \ No newline at end of file diff --git a/.sdkmanrc b/.sdkmanrc new file mode 100644 index 0000000..1024ab8 --- /dev/null +++ b/.sdkmanrc @@ -0,0 +1 @@ +java=17.0.17-librca diff --git a/.travis.yml b/.travis.yml deleted file mode 100755 index a5c5944..0000000 --- a/.travis.yml +++ /dev/null @@ -1,18 +0,0 @@ -sudo: false -cache: - directories: - - "$HOME/.gradle" -language: groovy -jdk: -- openjdk8 -install: true -script: "./travis-build.sh" -env: - global: - - GIT_NAME="Jeff Scott Brown" - - GIT_EMAIL="brownj@objectcomputing.com" - - secure: iGHTB09WUKZMbi9eT47C5QAaaM/EfT2B/Sc4AGAPVo71ivdbAKHL8ycrXcoGjjkSUbgao3UVrolLMb+ObLpKuGjA+/TjD8GGx7B2MaTYFCokcu38ptVtie4e90Tqse2UclTJkSkQkPfz1OvUoht/vXDYx0p3GQvuvq+EOAJ8ydpJ7ES4ygOCBAEtMAswybTuTep1Lo0VZA5dIgNBZt+afncOOKTZrOg0PS51gwNCBOwj0feQcMl4OSwfRCL7sPwf0Tm1Daghc8GPTJfqS6zc9F+M9xQ1pR54i+QLc6sZSbJ7FV1WsiLySNkDuLXqnX9IgRkf5R0HZnqrzYvUA5l0S5UpFJ3R67WsbjMBRTJbM8VZZ/tqIJjxrNhu2XvsOgeLOfgRsoG8SA8qzRuf5k2WioCx1bXZExv6j4Yso3jOCEIGAqOH1mXSycYro6thE6uMqXoot1ewwdKmi/hnOdldcVDsM/xNpdPUNm/79RJcARXKrK3vIuydZXKYuB4916SrdJC77vj9FWscE1yW7Qvk+P5PdxD/OC8F4jl+VWaoIbGFA0qip5s1ZdEo2RmsjjwQ4RmV6I3Ouyemzo/eBufe8LEl+ReSQ8Le5ZpYux6nZSCy4lu/rEzGWQYFvHFSjMMSWqwKYmII1CmUpBBh/uJvRlr4QNJ9oHV+GZeTGq8exec= - - secure: WlnyB+KinP4NZ8gvawNAr/ZpNgdDWAk9YFc5Xgj83vxjJD7wVhKMgdptUTLhkXFDh8M7uMOoOwMZGJtorZU6zd9Bgve4LrTOztvmVYn6FnfzqyZ5xtObBEo8zyKE9EKtZUmnWs2x40P0Oa4sOPTNjoI5dE+JL4vuWo5fwURYWKnoKQFOHxEUe5a5lhnRU4yax2WvqjdIjjEgvhRaMEDl9sVJ0YpL3HVkAIUuqVu/FeOE/emTiKxpkeKlU03vqYfrrIuAMiNP/HV4ytq3lljEBrmHN13K23uLsOu2Y2WKaMlwkozZhpbyI0HUkiuyUIGry4PKwlUt3dtxxYVdM5DDGjZqge42aMy6J6dx1rZm6nAuVhahnEhASUccAgSzwPD0g6LaPZY3x0Hj439RyGTs0urKrYZfodFagXkcbwlpoRJWvKJzfCwQ4uErdPjL4/7nY8Can9vczdeJJp08Hfmb6PeK8nsQCTVnBlFfo3dba/d0aouQQ2AFdhjh5jLh3iDk0ajiXnFu8MNcPw+G0m89MGrb5kZaORPnbt8YALxZcCKND+R2+5UG8NuOE9casEmcMRzVK5uXTCm/edfHDjEnHIMaMZebZ00FCnVrsmduZItjTxIW9UesomnuiIzcaqUrjydCz3oKg9Mxsd2Pk8FP6vfbaCtmodvfcJJGX/XuKQM= - - secure: JvjwJl2f+CtNwJFYovIGqyxJd1KtYsKmtIJmFmBqWT/RnRobKAL7as2XTxnuwhtrPEcbbRWFk6EHqbYR5bwEaW53MOFkCHnhlmnaRpkqsQ9k+rxm/xkjfE3XTLTvQotqycPg3f+oIzOtAoyigzbpJc5fJVEY85z4vpLduOgoAEA9s8iwDaVjEjKQ36yexMh2AExu/k3ZPzykPO1rgOA3lqCnvtTEe45FU/LDXBV3CwurDjTVwfB1cz9Sv7bhoI34t0WPChyVdmd5EBhFWo+uN8SpFjWWzIS0ITLQuCwHtE9TylgOYg00U9hFGDnM0HAX3cD6PXvGvpIPApHvlY8mINYUxgxL2r1fY42MRIXnXMLhx7IwnBfQbYjCcZFRlUZGm1gt1gatkH9WZS6wgfct1gl3kty4j2hG1utW1roDMhrUK4eueeONJJnkrhnFg17iyPxTUYMbHqMhxvSzV9LVJ7Xow07lQna9pVzcKMzFeYUK0iVQ2tmVPkHU+GDQBVaE26/yBHAe2HD4dGqwCLRyCl/+6BgKjjpY+hp4hJP7DdvEobB0hDfko7Xe8U03sru1/fdSAId5qInTWOC0wB7OJms5EC5CbQ7P6IwTT8wZE8T+1Xe62Qa8XNBTQGiB3ILPFp1Qt/qXpRZ5YPYPcOV7NjJO3Tho8HS/5Se6qUjoN6s= - - secure: EtRTXZIfaQnOznmEF5E5E2rBAwV13suj9cizZf06KIH2bgptfDMiRLvK8FGrbe0Obvx5I73dmzlmWBhL9CFhtuhOMVuxBNXVel5/hYAtavdOWPu8iSTygGEAY3HARbLuKQtIPiOQsXEV1c6WiBCzNjdBexZcX6lgGvVq+tnHuH0HFWUw+qLxAbrFezJwSfvl7NrzqDm/8ke9WpM4yLyWekHofwrInPiricVN2F+rjyL5mT9Ms+zqX/n9iQ6ZGty93+HDNeREtU28fhhs87GIFdj9WNtNH7GWlraVgGl5gNfbhkcTh/iLsbBWjxVbY49sj/zfNKTz1A4GpH2Zzed6EcA0jwsm9bXmO++ni/WcEDGXlFv7l7+bPkhcoUVNPD6ATwMbc3ydg4i8tImLrABFVBJD9YldOzyE7l99WZ+wI5oOXgaIh7iR8fd5l0O7h1QOIYAxAyywgmF6269STqIvuUAFQZKFBh++P57VeVSsDvvQ3rMmHEm3H+RWmFR5bksvH6/5wSbNlkqw9j/lnpSxfvL+BV4dFQht7r9mq1BQV4okgTYRZvMJA6iCcqBjRMZ4WAO8wtJFSoyyP1Ahnc5zbQGRmjp4Jf8aJygS3CaDIb7aUUQdil2pgs6mhw4l00th2xt43nO9J3lEhcXixc3dxNI4HovJtjuqILvGE1MvOzM= - - secure: g27i8w0cPKHP1Ku/hSzYlsqRHwqvdw0MhEl/H40cGQix023n7VwI5kGqufOD75ELnK6SPKsxBq1DP0atAVOi5DJrr3ne96veTiSQrNCDB1jPqDPNjX2gTHI6ND5oKUgg1RQMDZfhdq+1VIZauVxLuQMeUyyH9F5XC7GG0jtE1Kiw0/6g7cA9wIrf7FDVON6c4ImRRIncfClgi8kaj2lnVpCNixI7XUIi1eNNryO5Fbqx3wiK9lfX396pzi+UW86AKlIFOTTHOpHEsVToa01rHwokbaXS5ympoHwkfDTozW6ubJyA8x+/iH5aOwD7RZ573hlnW7WnXtf9erx38vJGNKcGKI8gW9Fhdkqxgiw3DYaoReY3rvTTRziMBpDfwyEd4PlbG2/UBlSqpuPOd8uC3HYQj24JcNgMldCs1W0uANmGPOkjMT4WZSsYbWlSgJN33dDn2lVZjgvFMNQuiRdi7i8f6EstsTyXP1XW37kZoOR/xHH7Ez1HCXNHhq59ZxGFB8P2y+BimXJX9VBxj+ifvtyY/lo9EnT1xixh5RsJ8Ug21+pNat9D4Q4QUsWtNoRybU0lYnHCvZSyi+ulvbpHXLl+tH1MuRor9xTMy8mnlmP/e0Au5V95G+PjI7dzkK5Ch/s4ayUzxFXk9mWMVfRhQkM/z5dEan2DvoCyoMaJbNw= diff --git a/LICENSE b/LICENSE index e8ccac6..c78d9eb 100644 --- a/LICENSE +++ b/LICENSE @@ -1,4 +1,4 @@ -Apache License + Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ diff --git a/README.md b/README.md index 3e8b0d0..79de830 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,21 @@ -## Docs +# 🧩 Grails Logical Delete Plugin + +[![Maven Central](https://img.shields.io/maven-central/v/org.grails.plugins/grails-logical-delete.svg?label=Maven%20Central)](https://central.sonatype.com/artifact/org.grails.plugins/grails-logical-delete) +[![Java CI](https://github.com/grails-plugins/grails-logical-delete/actions/workflows/gradle.yml/badge.svg?event=push)](https://github.com/grails-plugins/grails-logical-delete/actions/workflows/gradle.yml) + +The **Grails Logical Delete** plugin provides a simple way to implement logical deletion (soft delete) +of domain class instances in Grails applications. + +Instead of permanently removing records from the database, the plugin marks them as deleted, +allowing you to preserve data integrity, maintain audit history, and safely β€œundelete” entities when needed. + +## ✨ Features + +- **Transparent soft-deletes** β€” automatically updates a flag instead of issuing DELETE statements. +- **Query filtering** β€” excludes logically deleted records from queries by default. +- **Undelete support** β€” easily restore logically deleted records. + +## πŸ“š Documentation + +[Latest release](https://grails-plugins.github.io/grails-logical-delete/latest/) | [Latest snapshot](https://grails-plugins.github.io/grails-logical-delete/snapshot/) -See [grails-plugins.github.io/gorm-logical-delete/](https://grails-plugins.github.io/gorm-logical-delete/). diff --git a/build.gradle b/build.gradle index b1d72b7..a635cc5 100644 --- a/build.gradle +++ b/build.gradle @@ -1,88 +1,14 @@ -buildscript { +subprojects { repositories { - mavenLocal() - maven { url "https://repo.grails.org/grails/core" } + mavenCentral() + maven { url = 'https://repo.grails.org/grails/restricted' } + maven { + url = 'https://repository.apache.org/content/groups/snapshots' + content { + includeGroupAndSubgroups('org.apache.grails') + includeGroupAndSubgroups('org.apache.groovy') + } + mavenContent { snapshotsOnly() } + } } - dependencies { - classpath "org.grails:grails-gradle-plugin:$grailsVersion" - } -} - -plugins { - id 'org.asciidoctor.convert' version '1.5.3' -} - -version "2.0.0.BUILD-SNAPSHOT" -group "org.grails.plugins" - -apply plugin:"eclipse" -apply plugin:"idea" -apply plugin:"org.grails.grails-plugin" -apply plugin:"org.grails.grails-plugin-publish" -apply plugin: 'codenarc' - -sourceCompatibility = 1.7 -targetCompatibility = 1.7 - -repositories { - mavenLocal() - maven { url "https://repo.grails.org/grails/core" } -} - -dependencies { - compile "org.springframework.boot:spring-boot-starter-logging" - compile "org.springframework.boot:spring-boot-autoconfigure" - compile "org.grails:grails-core" - compile "org.springframework.boot:spring-boot-starter-actuator" - compile "org.springframework.boot:spring-boot-starter-tomcat" - compile "org.grails:grails-web-boot" - compile "org.grails:grails-logging" - provided "org.grails:grails-plugin-domain-class" - profile "org.grails.profiles:web-plugin" - testRuntime "com.h2database:h2" - testRuntime "org.apache.tomcat:tomcat-jdbc" - provided "org.grails:grails-plugin-services" - testCompile "org.grails:grails-plugin-testing" - testCompile "org.grails:grails-gorm-testing-support" - testCompile "org.grails:grails-web-testing-support" -} - -bootRun { - jvmArgs('-Dspring.output.ansi.enabled=always') - addResources = true -} -// enable if you wish to package this plugin as a standalone application -bootRepackage.enabled = false -grailsPublish { - userOrg = 'grails' - githubSlug = 'grails-plugins/gorm-logical-delete' - license { - name = 'Apache-2.0' - } - title = 'GORM Logical Delete' - desc = 'Adds logical delete capabilities to GORM' - developers = [jeffbrown: "Jeff Scott Brown"] -} - -groovydoc.docTitle = "GORM Logical Delete ${project.version}" -groovydoc.destinationDir = new File(buildDir, 'docs/api') -groovydoc.noTimestamp = true - -asciidoctor.outputDir = new File(buildDir, 'docs') -asciidoctor.separateOutputDirs = false - -asciidoctor { - - attributes 'experimental' : 'true', - 'compat-mode' : 'true', - 'toc' : 'left', - 'icons' : 'font', - 'version' : project.version -} - -task docs(dependsOn:[groovydoc, asciidoctor]) - -codenarc { - configFile file('config/codenarc/codenarc.groovy') - sourceSets = [sourceSets.main] } diff --git a/buildSrc/build.gradle b/buildSrc/build.gradle new file mode 100644 index 0000000..fdcea0f --- /dev/null +++ b/buildSrc/build.gradle @@ -0,0 +1,69 @@ +plugins { + id 'groovy-gradle-plugin' +} + +def versions = new Properties() +file('../gradle.properties').withInputStream { + versions.load(it) +} + +repositories { + maven { url = 'https://repo.grails.org/grails/restricted' } + maven { + url = 'https://repository.apache.org/content/groups/snapshots' + content { + includeGroupAndSubgroups('org.apache.grails') + includeGroupAndSubgroups('org.apache.groovy') + } + mavenContent { snapshotsOnly() } + } +} + +dependencies { + implementation platform("org.apache.grails:grails-bom:${versions['grailsVersion']}") + implementation 'cloud.wondrify:asset-pipeline-gradle' + implementation 'org.asciidoctor:asciidoctor-gradle-jvm' + implementation 'org.apache.grails:grails-gradle-plugins' + implementation "org.apache.grails.gradle:grails-publish:${versions['grailsGradlePublishVersion']}" +} + +gradlePlugin { + plugins { + register('codenarc') { + id = 'build.config.codenarc' + implementationClass = 'build.config.CodeNarc' + displayName = 'CodeNarc Configuration' + description = 'Configures CodeNarc for Groovy static code analysis' + } + register('docs') { + id = 'build.config.docs' + implementationClass = 'build.config.Docs' + displayName = 'Documentation Configuration' + description = 'Configures Groovydoc, Asciidoctor, and a docs aggregator task' + } + register('grails') { + id = 'build.config.grails' + implementationClass = 'build.config.Grails' + displayName = 'Grails Build Configuration' + description = 'Configures Grails for the build' + } + register('java') { + id = 'build.config.java' + implementationClass = 'build.config.Java' + displayName = 'Java Build Configuration' + description = 'Configures Java for the build' + } + register('publish') { + id = 'build.config.publish' + implementationClass = 'build.config.Publish' + displayName = 'Publish Configuration' + description = 'Configures publishing' + } + register('reproducible') { + id = 'build.config.reproducible' + implementationClass = 'build.config.Reproducible' + displayName = 'Reproducible Build Configuration' + description = 'Configures the build for reproducible outputs' + } + } +} diff --git a/buildSrc/src/main/groovy/build/config/CodeNarc.groovy b/buildSrc/src/main/groovy/build/config/CodeNarc.groovy new file mode 100644 index 0000000..b2fcdb5 --- /dev/null +++ b/buildSrc/src/main/groovy/build/config/CodeNarc.groovy @@ -0,0 +1,57 @@ +package build.config + +import groovy.transform.CompileStatic + +import org.gradle.api.Action +import org.gradle.api.Plugin +import org.gradle.api.Project +import org.gradle.api.plugins.quality.CodeNarcReports +import org.gradle.api.tasks.GroovySourceDirectorySet +import org.gradle.api.tasks.SourceSetContainer + +@CompileStatic +class CodeNarc implements Plugin { + + @Override + void apply(Project project) { + + project.pluginManager.withPlugin('java') { + + project.pluginManager.apply('codenarc') + + def codenarcAnalysisTarget = project.configurations.maybeCreate('codenarcAnalysisTarget').tap { + description = 'The CodeNarc Semantic Analysis target.' + canBeConsumed = false + canBeResolved = true + visible = false + } + project.dependencies.add( + 'codenarcAnalysisTarget', project + //project.dependencies.project(path: ':grails-logical-delete') + ) + + def sourceSets = project.extensions.findByType(SourceSetContainer) + if (!sourceSets) return + def mainSourceSet = sourceSets.named('main').get() + if (!mainSourceSet) return + def groovyMain = (GroovySourceDirectorySet) mainSourceSet.extensions.getByType(GroovySourceDirectorySet) + + project.tasks.withType(org.gradle.api.plugins.quality.CodeNarc).configureEach { + it.configFile = project.rootProject.layout.projectDirectory.file('config/codenarc/codenarc.groovy').asFile + it.source = groovyMain.asFileTree + it.compilationClasspath = project.files( + mainSourceSet.output, + mainSourceSet.compileClasspath, + codenarcAnalysisTarget + ) + it.reports(new Action() { + @Override + void execute(CodeNarcReports reports) { + reports.xml.required.set(false) + reports.html.required.set(true) + } + }) + } + } + } +} diff --git a/buildSrc/src/main/groovy/build/config/Docs.groovy b/buildSrc/src/main/groovy/build/config/Docs.groovy new file mode 100644 index 0000000..6851daf --- /dev/null +++ b/buildSrc/src/main/groovy/build/config/Docs.groovy @@ -0,0 +1,92 @@ +package build.config + +import groovy.transform.CompileStatic + +import org.asciidoctor.gradle.jvm.AsciidoctorTask +import org.gradle.api.tasks.javadoc.Groovydoc +import org.gradle.api.Plugin +import org.gradle.api.Project +import org.gradle.api.tasks.javadoc.GroovydocAccess + +@CompileStatic +class Docs implements Plugin { + + @Override + void apply(Project project) { + project.pluginManager.apply('org.asciidoctor.jvm.convert') + + project.tasks.withType(Groovydoc).configureEach { + it.access.set(GroovydocAccess.PROTECTED) + it.processScripts.set(false) + it.includeMainForScripts.set(false) + it.includeAuthor.set(false) + it.destinationDir = project.layout.buildDirectory.dir('docs/api').get().asFile + it.docTitle = "Grails Logical Delete ${project.findProperty('projectVersion')}" + it.noTimestamp = true + } + + project.tasks.withType(AsciidoctorTask).configureEach { + it.sourceDir = project.layout.projectDirectory.dir('src').asFile + it.outputDir = project.layout.buildDirectory.dir('docs').get().asFile.absolutePath + it.baseDirFollowsSourceDir() + it.options = [ + doctype: 'book', + ruby: 'erubis', + ] + it.attributes = [ + 'examples' : project.rootProject.layout.projectDirectory.dir('examples').asFile.absolutePath, + 'compat-mode' : 'true', + 'copyright' : 'Apache License, Version 2.0', + 'encoding' : 'utf-8', + 'experimental' : 'true', + 'icons' : 'font', + 'id' : "$project.name:${project.findProperty('projectVersion')}", + 'idprefix' : '', + 'idseparator' : '-', + 'lang' : 'en', + 'linkattrs' : true, + 'numbered' : '', + 'producer' : 'Asciidoctor', + 'revnumber' : project.findProperty('projectVersion'), + 'setanchors' : true, + 'source-highlighter' : 'prettify', + 'toc' : 'left', + 'toc2' : '', + 'toclevels' : '2', + 'version' : project.findProperty('projectVersion'), + ] + it.jvm { + jvmArgs += [ + '--add-opens', 'java.base/sun.nio.ch=ALL-UNNAMED', + '--add-opens', 'java.base/java.io=ALL-UNNAMED' + ] + } + } + + project.tasks.register('docs').configure { + it.group = 'documentation' + it.inputs.files( + project.tasks.named('asciidoctor'), + project.tasks.named('groovydoc') + ) + def outputFile = project.layout.buildDirectory.file('docs/index.html') + it.outputs.file(outputFile) + it.doLast { + def redirectPage = outputFile.get().asFile + redirectPage.delete() + redirectPage.text = ''' + + + Redirecting... + + + + +

Redirecting to documentation...

+ + + '''.stripIndent(20) + } + } + } +} diff --git a/buildSrc/src/main/groovy/build/config/Grails.groovy b/buildSrc/src/main/groovy/build/config/Grails.groovy new file mode 100644 index 0000000..5b5379f --- /dev/null +++ b/buildSrc/src/main/groovy/build/config/Grails.groovy @@ -0,0 +1,20 @@ +package build.config + +import groovy.transform.CompileStatic + +import org.gradle.api.Plugin +import org.gradle.api.Project + +import org.grails.gradle.plugin.core.GrailsExtension + +@CompileStatic +class Grails implements Plugin { + + @Override + void apply(Project project) { + project.pluginManager.apply('org.apache.grails.gradle.grails-plugin') + project.extensions.configure(GrailsExtension) { + it.springDependencyManagement = false + } + } +} diff --git a/buildSrc/src/main/groovy/build/config/Java.groovy b/buildSrc/src/main/groovy/build/config/Java.groovy new file mode 100644 index 0000000..9ca5765 --- /dev/null +++ b/buildSrc/src/main/groovy/build/config/Java.groovy @@ -0,0 +1,30 @@ +package build.config + +import groovy.transform.CompileStatic + +import org.gradle.api.Plugin +import org.gradle.api.Project +import org.gradle.api.plugins.JavaPluginExtension +import org.gradle.api.tasks.compile.JavaCompile +import org.gradle.jvm.toolchain.JavaLanguageVersion + +@CompileStatic +class Java implements Plugin { + + @Override + void apply(Project project) { + def raw = (project.findProperty('javaVersion') as String ?: '').trim() + def releaseVersion = raw.isInteger() ? raw.toInteger() : null + if (releaseVersion == null) { + return + } + project.pluginManager.withPlugin('java') { + project.tasks.withType(JavaCompile).configureEach { + it.options.release.set(releaseVersion) + } + } + project.extensions.getByType(JavaPluginExtension).toolchain { + it.languageVersion.set(JavaLanguageVersion.of(releaseVersion)) + } + } +} diff --git a/buildSrc/src/main/groovy/build/config/Publish.groovy b/buildSrc/src/main/groovy/build/config/Publish.groovy new file mode 100644 index 0000000..89eddd1 --- /dev/null +++ b/buildSrc/src/main/groovy/build/config/Publish.groovy @@ -0,0 +1,29 @@ +package build.config + +import groovy.transform.CompileStatic + +import org.gradle.api.Plugin +import org.gradle.api.Project +import org.gradle.api.provider.Provider + +import org.apache.grails.gradle.publish.GrailsPublishExtension + +@CompileStatic +class Publish implements Plugin { + + @Override + void apply(Project project) { + project.pluginManager.apply('org.apache.grails.gradle.grails-publish') + project.extensions.configure(GrailsPublishExtension) { + it.organization.name.set('Grails Plugins') + it.organization.url.set('https://github.com/grails-plugins') + it.license.name = 'Apache-2.0' + it.title.set('Grails Logical Delete') + it.desc.set('Adds soft-delete capabilities to Grails domain classes.') + it.githubSlug.set('grails-plugins/grails-logical-delete') + it.developers.set(project.provider { + project.findProperty('pomDevelopers') as Map ?: [:] + } as Provider>) + } + } +} diff --git a/buildSrc/src/main/groovy/build/config/Reproducible.groovy b/buildSrc/src/main/groovy/build/config/Reproducible.groovy new file mode 100644 index 0000000..331826e --- /dev/null +++ b/buildSrc/src/main/groovy/build/config/Reproducible.groovy @@ -0,0 +1,31 @@ +package build.config + +import java.time.Instant +import java.time.ZoneOffset +import java.time.format.DateTimeFormatter + +import groovy.transform.CompileStatic + +import org.gradle.api.Plugin +import org.gradle.api.Project + +@CompileStatic +class Reproducible implements Plugin { + + @Override + void apply(Project project) { + def buildInstant = Optional.ofNullable(System.getenv('SOURCE_DATE_EPOCH')) + .filter(s -> !s.empty) + .map(Long::parseLong) + .map(Instant::ofEpochSecond) + .orElseGet(Instant::now) as Instant + project.extensions.extraProperties.set( + 'formattedBuildDate', + DateTimeFormatter.ISO_INSTANT.format(buildInstant) + ) + project.extensions.extraProperties.set( + 'buildDate', + buildInstant.atZone(ZoneOffset.UTC) + ) + } +} diff --git a/config/codenarc/codenarc.groovy b/config/codenarc/codenarc.groovy index 47b8bc2..73959c6 100644 --- a/config/codenarc/codenarc.groovy +++ b/config/codenarc/codenarc.groovy @@ -376,7 +376,6 @@ ruleset { UnnecessarySelfAssignment UnnecessarySemicolon UnnecessaryStringInstantiation - UnnecessarySubstring UnnecessaryTernaryExpression UnnecessaryToString UnnecessaryTransientModifier diff --git a/examples/app1/build.gradle b/examples/app1/build.gradle new file mode 100644 index 0000000..77a4567 --- /dev/null +++ b/examples/app1/build.gradle @@ -0,0 +1,77 @@ +version = projectVersion +group = 'test' + +apply plugin: 'org.apache.grails.gradle.grails-web' +apply plugin: 'org.apache.grails.gradle.grails-gsp' +apply plugin: 'cloud.wondrify.asset-pipeline' + +dependencies { + + dependencies { + + implementation platform("org.apache.grails:grails-bom:$grailsVersion") + + implementation project(':grails-logical-delete') + + implementation 'org.apache.grails:grails-core' + implementation 'org.apache.grails:grails-logging' + implementation 'org.apache.grails:grails-databinding' + implementation 'org.apache.grails:grails-interceptors' + implementation 'org.apache.grails:grails-rest-transforms' + implementation 'org.apache.grails:grails-services' + implementation 'org.apache.grails:grails-url-mappings' + implementation 'org.apache.grails:grails-web-boot' + implementation 'org.apache.grails:grails-gsp' + implementation 'org.apache.grails:grails-layout' + implementation 'org.apache.grails:grails-data-hibernate5' + implementation 'org.apache.grails:grails-scaffolding' + implementation 'org.springframework.boot:spring-boot-autoconfigure' + implementation 'org.springframework.boot:spring-boot-starter' + implementation 'org.springframework.boot:spring-boot-starter-actuator' + implementation 'org.springframework.boot:spring-boot-starter-logging' + implementation 'org.springframework.boot:spring-boot-starter-tomcat' + implementation 'org.springframework.boot:spring-boot-starter-validation' + + testAndDevelopmentOnly platform("org.apache.grails:grails-bom:$grailsVersion") + testAndDevelopmentOnly 'org.webjars.npm:bootstrap' + testAndDevelopmentOnly 'org.webjars.npm:jquery' + + runtimeOnly 'cloud.wondrify:asset-pipeline-grails' + runtimeOnly 'com.h2database:h2' + runtimeOnly 'org.apache.tomcat:tomcat-jdbc' + runtimeOnly 'org.fusesource.jansi:jansi' + + testImplementation 'org.apache.grails:grails-testing-support-datamapping' + testImplementation 'org.apache.grails:grails-testing-support-web' + testImplementation 'org.spockframework:spock-core' + + testRuntimeOnly 'org.junit.platform:junit-platform-launcher' + } +} + +compileJava.options.release = javaVersion.toInteger() + +tasks.withType(Test).configureEach { + useJUnitPlatform() + testLogging { + showStandardStreams = true + events('passed', 'skipped', 'failed', 'standardOut', 'standardError') + } + // For debugging + //jvmArgs += ['-Xmx2g', '-Xdebug', '-Xnoagent', '-Djava.compiler=NONE', '-Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=5005'] +} + +assets { + excludes = [ + 'webjars/jquery/**', + 'webjars/bootstrap/**', + 'webjars/bootstrap-icons/**', + ] + includes = [ + 'webjars/jquery/*/dist/jquery.js', + 'webjars/bootstrap/*/dist/js/bootstrap.bundle.js', + 'webjars/bootstrap/*/dist/css/bootstrap.css', + 'webjars/bootstrap-icons/*/font/bootstrap-icons.css', + 'webjars/bootstrap-icons/*/font/fonts/*', + ] +} \ No newline at end of file diff --git a/examples/app1/grails-app/assets/images/advancedgrails.svg b/examples/app1/grails-app/assets/images/advancedgrails.svg new file mode 100644 index 0000000..8b63ec8 --- /dev/null +++ b/examples/app1/grails-app/assets/images/advancedgrails.svg @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + diff --git a/examples/app1/grails-app/assets/images/apple-touch-icon-retina.png b/examples/app1/grails-app/assets/images/apple-touch-icon-retina.png new file mode 100644 index 0000000000000000000000000000000000000000..d5bc4c0da0bf34d32c86eee86f14845f1e5bf412 GIT binary patch literal 7038 zcmV-^8-e7BP)Py5I7vi7RCodHT??FD)4Bh?edZBlroEM*RKi6)f?Kb8q)LcnCT_hiqMrTTqHe32 znM_IBd#Arvq{$@JYJ*ZjdP!V@y3I@?1QkUil%j){#G{B_%!It=>~;U&x6e7V&zb!^ zXP=WZk@cH1d+oKp^{wx*-s@XyGYw*Ay4pWOX3d^NHh#P(88d>I8^DtDGkCHQKat^H zV(Nv@`5A+EUWJHmBCDHtXg4>t|DVDR!hbwiXf_LfUQhJ;VPx6IKw(EgF{2EbVM)@@ z#P31z&qKivlWjdnwDaRU;nMX<=20$ORjo*~sAVrQbP9fBpuEwBXsdDw!V52ivldEy z3^n;d{1))|nSb$utA?0niGK|*qnNZxh#+XB@K}cB|nE8)T?5$L9=JA-B?`6ZQ z;aN3`MD1%Pn=VG5)ocvaR)cW-Xkey|=#x9iqU%sgJN@7)8NNvsi5kbWs0~eLdJ2?x zU?mw(DlaH?1#xOBji0^%H5RS^rg57@k;dGSqOK<=lCh_N^Sw6>>&fN{LB2?AO{JzU z-p+h_I$7-D*eb3_vv~4oO53wh8}|<k=L}V_7Asub3ir5VMx1V|af) zn$e2oTS;MN^FnirnGw$+s{1ZaxN3DayqwR92-I}8TnN^tfxCYyCo|QC8}rilG0MGy znr8f}-2G=+73r+J^v*#u`ju)EawX}367M5!IKP}LM`cr_>1;h5gPD04%#5g{(5f{r zDD!npSZGUlJO;q&^+H>M=N`8 zMPGPEGURQZSkQOQr;!ck@~HL=NpYB%BF)nFp|q~|Zj`pMNfEa><3UrqgoZSn%wyWu zB*AAQiZlza7*4#Q6G}WN38FqJIH1g@$ka6_F-0j^k>-Jxk+h!fM{~NzCq)5C@`+~l zWvZv+_}G~%OTk-`BDE&chGkonSc(cTmK9}E-RM$kX3>f?kG3^XZ+dCm<`m7zt6=+Y z7{SR7!Y=Ud0vto|+$9fZwM8L|@smzD4yzuhW-xJx^qJVzAWKPD2{6 z%!Gqq8t0W>!c>85{TMV~<5q@Q)VdAmZ3To(JGSvluQ+4?G@{}7Vn(wyL5RKLKnzo7 zt4uK>DG|-a25>o%n`bWY!UqLox=BxA)H{*a_UwrlC`p{1hCuopf9}7$OO2M_Z_UD% z4$#q(i*sg32mHtB6E9|K`jJ} z+Ri}HT|i{{g~juq6^dLzV_QDQ#X5aKTo@@c@YC)+IJ3A1ieh~|bi?&|qE zYD!~%3Vpi)N<=f4al?ns<<&h?0sEP3P;yUxFk4W~(#bo}x^z1h&0z>;o*(u8;Zpo$ z4%*}|+kx3Xqi<=+|MBb`yxYTcEe1aw7?fNI_!D{jtpAm0^s5-{^1*IU^`{v9A>u_4 zY|bKk&AIZ9bXd4HcZ)w+XO9OhTEMw!#$D7(|^X#O=a!KJko$PTJ7qZ ze4#XjvB{~SeoHgMSTM+|Sg&yNtXXcHFu?jU8pTW8VdCeeSwAWa)Ax02t^~u|x#J+b zXJ8QZh=!RpXQM9HV?6bBykD(h7M^MPH+Py+{m5e!uj{!J$~+%~qYwDVY3~E)ujFSs z+xBEi-wFBc8#f=3IYahHcYRpe&>E?eeq*i*L*yB6*YpoUnBMe+i4bTOx9zAF1{qkh zu@zVxVE_RLFjet3ZMB(rc*pZ=Cy zv8_99fIr>09HzoAp~Qb$`#@9^TGa$kM|kRHh$@k*15&GzEz-lW7h5y-Og%N1ejPck;ODPK{5!c9Kbx3=;Aj z8~X?Z+^d0h{dLUizlXH>>!8RaFHz2-6L7g;#>OVZgN=nGMgIu9mj!5WNtGMy=>VN_EhQ>c#_)C(*KUf)$uMJ>W|Uf{flm30?w=>{o#Z{fYT3o;xAz zU@$tujU2Guh?il!xPwqnY8@|>pgSn$9p{}JQCs&y)AnRG9?wIaTIvePc#-xE;Kfhf zFu?6zfNwttOV>mTU^7Wv4?~}l!XY8jN8%p}7QKKcM(F%G6ilH*2RyD+C3)FOou+fu z0xaH&>}**9W)3KT-SS!m5%~KD2yJUNGxK1 zqY!Azd$mcxmMyV7m)ErF>d89umb*X7gWmE|6%x>?))3y&eb_d?My{oaTa;K8x(Xyh zbp&0xfePl4Mt2F^0LA)IYA`D0S1}@$$+y_&$ON$u@7P0x9}^sm9x>>{OwhouTUCsA zgkd()|Bh5Ti^Nk-IrTfYzdtnJm)GJTDlAPte0DJeLsBGl;$(4x#gC`Kwz$S=yi|(J zk1^H2_LU*Xmqo@kfWY#d*)^ZMQr=Wo*JLp`#No}{-?q2e*!My7T+Mpws`Vz)W*6*xp3)B6{(jI86)eE$vH!xPY&5;fXOz=%L6)d zE|SVNt-R`Ri~_bwQe42deUuH+B=f9lT*j2X0a3u9>I}M*V5eziGo}D0nb*|%Lz6+Y z8DL>9k1R5}a}+6eyOqPG`RH<>9h5i?l}8pC-DMQX{U#YnN~v5mJxLlXfD4=9yJDa- zvMgEQaz(*wiOC=`8CH%=FttAr`*FLK1NWf9Y13)tqE~b$I>hRO+;>_=$LX`nh<$LN zHugDXB@{YD(<+zNmeCo$K#vQQF^g(OEw`IsbeZ^wHJ@i&6p5RM%jmRkc<<%&%a}!# zqqf`2Pay9p3%ZdyVXQA<(VOK0+6`El#rX+c1joWR7QCS?gyV@`F?U0vEQr%i_&)}x zuf7j^ow=~WURQpmswW4r4>D;pPAJ0KI%H?wa;TF9cP!aqL0Y4nX`l=|@iQr`&!+%e z2dN$lAx5$F8Ce711_ycQ|F?Oy!s1B1zF`j9k5$s>qHNxr zGy9K&W01kZIC?x zgD$^_i(@ds>rps_{VT0OL92+K%thJAn(Pc!(U(rrHB6J>@luVpm)G$NW zV7I(%s~vgyR_H~K(XH#b8C{4D2$fg4Rip^#$T-H;s4$a}rZsm=IxJ6g-VL;RO;M{9 za=Qi2UY!62-v%BTWZj10xgPKd%eUI&Q3G+we(w!XqOM{IkIJj?T#SQ^foo{wq|l)l z4@Fj_B9;r~ADdOw-lcQ&n~p`3FM(n(xI*$Yza9K-a`cgm!oYeelNgIBDFln2WxE3Mt3WUPGy?r8M7$`BG6$kQ?u& z@M{y;_4r+=k=jZLPgGlbWY!?6D4ITea|ZD395XFyWQ?XYhn(p@&@3L8dynbi2{Zmm zmUSpf?*r1zqHxSMU6Xzqcb*zE+eE?jMA+)yhfo-uGne2+c^W(IZ^dThoZNDzEwOME z)wCi95HAaIbIb$Z3tx5cp$L8EyIAcQ2W9>keTofh*UM;a@1r=GyN6GbstMMCnoV*g z+95)_@Swa1PenSFCEGWvY~$iU_lRq&Tdo2@m&Z*j@Mub&CsRMBlv@o24`?_9kLy9l z0a!Ko7{~6~Y4{#j>&apPs1hfaQcybmQ_y+3kOT!jjmJf>-~D4LB&ux_Z$Rz#zAG;b zH|UrqePnkSpkF*n&nV*cOHJ35&-%ec30FZh9eM~Z0lW-#{UaEoPp8%0Ph)5Om?-JX z4I`FnC~V)MzVu&2i73FUab*76Bs7<*M3J4yut+63yqj*hNd9mL?y*vYp`Cn9?FqPT z>bX+zNLuGMMY(Io#%+o;9HkAknX7S2vXC_QXqRAN4M_a6Fu4x3>2ez1a829=Vz0b)7~b>d zY3PvU(!e9puPEaOroX9<}NKDB>pqxA>)FQ9R^$PVM;gL zQDo;N8AcyfGFBG?AYO(o(QBx#@vdT9jP{nrSwPVHSktr=t;8-t z*#P$uls3N(f(eT=OQviO7}P{u>>J>e7NIF~S8M=iE=qYeat{z>P%f9zouf$ky0?57 zx=aowy$-c!fmt%?^I=(l`Gat|(0?OMhPL1ofI9;h7aboA6%e~|{ZAlh-F6Cq(kP`# zXcHPkoZJejJSTku%ayOAvz;H8AQhIj{W{EdK_%=(i5?2Gt*N1C(=;8h^`UTX0Nci* zMtda1yOWu&?~cOAG1hL5(gqsccs%{44_A=J*un5b5)ufVW&7=Z=H(OZ<##8cPs_Vs z)t#j*&!N;G;|`1BN3*yeb$&E93Tz&Xz46;oYrZ?3&}h0Y-`ySN_Gg;w@^8_(bmms% z0yi<`@I&A*?F%@EX`3$^_E^QM#ua0L5LcY;(*cz0Uy2hnP%ILm;-JObbOR(Ev1BDW%Z zdR?wkDWhY1wLf+ai1SY+F)|3j=o1AsqDym$OJf1FrU+}Y<#Vbd=*pc#o+6!_l(^Fk zPzJw0lcsn9WcCHoHAQr+M;;;qdw|*2J079gAb%dS5G6gi(|zlepvqIE;h zjFPmTw*CBHeL;o{ebF~AECO~bVIX<&K}};G;J_E~0V5UmrxiRZuLF}N6XKzx0g7~N zP<6vJnM~bS3_57JhT6wrP4)u}xQe^9$Z-)|G{TiM+FQZ#W(k0mcIh^D2)}|Df9Xc<^pahQn!$#H$-=M8A)2 zycIcq`T~p^{F{!5dAh;0gC4?j-C>^oRO)7Jx;!HT16Kyd7DgU><+~`|>k5Oz-jz11 z_U14+04neBAfKL3td=;r@8&lRLMK*JJ`2ojL)U;>v z0s~&SY5GzSE*5H$oHk*#))SF{-9T3AQTJ<@Uuc5m!$V2JT z;qX=^H;V;%5VcLPCn-y#Ob4 zpt>jscE1KHQUSN3HZc3XJ5d}zO7UAXzTjKQQ4ru8S15@+Q9!Y-b7wTTe5lN%`l0+# z?>F%Ap_qGk#|DIgca=c^NKlj``?~K2!AM-FB9&R@E5Z=@#vAi#y8om7edGNic%>L8 zg&r2dNbX~tx46v@O=!4S$3=2xYaBj~Z!jgiiwuf&kbM*baL6It=^AENE9@_0oY`}s zMTz_frjq|fcHOOEL}jeGzlK`&eci-$;qmfUVen`n z)}zDuF}}fdQ||LkK8bYSRjlz0{Sb27#s@4g7;?LhnmSIzGj!=Ok|HHHH5Igbo(9Gd zfw>1nYNu^jhVB^l<7+sU^`U|r{j4FrTx0eH{(lZYkr$Q0x38Y?%8QBj0&~#+#Afji z-tc)@?nUA1^ z)f8sDDc+&Tg)jv?zSO+cqI|7ci9)n>sko9zj~WPzdj4`8+u+bX(oL9yn5VeIHmyI# zoGyUS%s60907!+ScuE{43URR%soXTp=s<~yKYB}86rds2oGvu`ly$W>)uDjp1;8kE zK0(HDbBV1;?RZBtoQLOmn6=(xKySi8_rc3$4nesk%FJ) zGq*z;PDTg2JZ650fTQNDCVS`ki6C<$Nm2Ag2zR;!{7~LgDpU6-gk#_>N|AyedxzM_ zt)GCVKo+?x>)85L)}AxbjK_5>fk1NxO^M zTIDfE(IyQC4Flwrc$CUcWH{lfqV1K0sFD;Z2>95UD~akxSG1W;r(1N9LI_CE={bIz z&rA?yAS!K6MRqE~K+w?0So6prY&beL6eq9lhJeQg!6g-kl6((s)o_8Bha`bBbC1D% z(7$UE_`+~NiA%Ik)GjIvIy^oxMM_q>+_kFr76_vvr5eJ(H=Fs_RD<1kXG1!iV;6*8 zcC`4(qj1>fe5}Zvfu)%`S9ICDD1`G#MyEv%Y`h3dRFRT_HhEo>Tj84uf5ldSz{opT zGFy(NFEc(g{{wLh2 z3`7>q9e*g7D9TnNvjjU$ZL3^yf-{UI9Gb}@97Ip_h41T4CruqUB;Ax2ilOX^%x389 zFyMeFoAXQA-pS&t=4WPmZ4S1@+X_wl%SCs^6q#iJhgnBc+MbP?8sBOli=>L5sCmzl z7)pt~Qw3ec6)C(qcbq;s5hKVc7!2)QL4M1Y6$JUB+5)Q?slT;bxd_-Kip(;j{pX^# zQ*imi6g0O7X2S>56V2!f;?z_U>->bL6mjIRNfqfP6gOowq{HzV|RadLGJqnDj;|TqHH&QkXzizZ*1)^b)0Wo1=Wl zRK9_U&nFs#Px=zez+vRA>d=TWgF})fHar+;0*7Ms%?-`0~9EOJm$_ltKZt^p2NB4 zoO|xM!$ZSP?(DtST6;bAUVH7w*~4@OlDs?nJUW`biEO!whzF6#<-~j*L{$<=7r>69 z+zR+Xz;^=HLKc5aT}t2PQS%O-Zk70SF_gyH1Buf2Lg5J{JWNQeVtF&MO)MXw6J94O zeVME0{i|3WQS_&5q%_T}BFW4G^U)%7%KTgX?o-WvQI35J5F+$oUI)0{3==H4|3Jq{}dy$ zC?jRv%#oC_U&HA2E=I`dq{XQ2rIa<1Z(Zju%CuNR0)C4VvYPS);jIHKEO@HvU0vF1AzkzKd&OPYq36H zQ-B)NKd_{(#1g5VvLEG_(Cz{7)6IiP4h=0Qwy;r)ak7VFV@E%4b zB}+Z}LItMneDk9Hl#!=|>@{L%8ZnAx;by$d(L0O@87b>#^)<^x6eEi_XIYqDV~a2< z)_7)XqQ|5cjoSr)OGnZMc7%gK>+j!Ur(T_lt(h0@>GQSm2L z$c4esPQV%Ck$3geX&F4!bxi?(9Xg?M0NgePZAf&@6uQv^Bq@mf5Xj6C&5WdT;BET? zg1wPV!spve15d#alwG0+Z)YUAZL+{|3Mma2Tmh=BplgR)WiT9QWnlo3W9TsVoUzri zN!hx`*zAO|H9yV=hY&+x1EyuHPl(D;SyJGyfwBbZ-v$vs_Si-gbimkZn+((wj*&ul zM9_sn%*{Bsz7j^9T?nFQe;6HWq|wN+>j}$TI^j+xk(z~~S|TQ=fk-dLk|e!r!ljy} zw`%aWob@tqd*N&I;?5>QD3KnBFqtdp#NjCk$VHnm*BJ;>a-b6_3Vncj&cf{iJs8XJHM(#gz7CCU2}knAhH(X zT=$ey&i#vH*89|WSP!TRb1`S3)|y@ftv|+bZ)B7-X{>t~`{TI*-1!!UEn<$@g9C)V zD%U~Klk(<6Lw&$AUc~|*t&%iS1Z$rh zCD^BdXJ-^nVF<@nw6En2SzFgNAC1||mUX%w#6zj6wn-Xh-r7-PRbfQ$duUhd2jJI( zN>W$F?UjvQs-NnaFTpBeC1x zLWg`9yg%{Nk==d+wuyIQB(KEjYb8cyc@93Jf^A|a7`P5?^a8CfI_Rf{PXw)k%so^? z?IzJFQoumN0$;G0e@QJrbnN zsUL4$0lFW#FqD62_<)}ab8{^AcN_U6i8koQF8xHW3@}1lVZd;gS)m**un$**{mjjo zw*O#pXF9Zd4-D)PY>RX6*Tj_e0V1-%xW*!6v}$ zYxy_yOuRNj&uzHvzgm|NB>hI8JQ+a;KflatacK9IQ3fg=Bh!_n^(P)}PJ!>vsro90 zANYD8$ScM`r=4S0)<`@;Fn1yz%ff|>mG+2AOK}|%lY}nHgUttb$WuNUetCJA`N57o zr7tVY7@2)#vC&TySB5KPsAs1=9!*K-FXKCjPk6LB1<3=4Z)vBORqXc2AN64q3B8FcoS0vXAc}&yWO@b+C+1yYpa%)_ien;rq29c6x{FuzYq>`jr@rIVPn)~S-%p8(&i|7Lp84JN01lZJA;qkRb7sT59GV`s9g0|JN&pDBMm~| z$>=kZyvZId_z)9B%leutu%W&&2*+gqLm1{j%oPfgiGVpY;A8mZ0I?i3%)AsQ|Iq=k zS2cP=e-Dr?VD!Vue6iJMBvBt?-GxaspzEkeEL5@`b{xx}p(n`0Ey#O2Q>YG?6p64B)$Qa$q- zs7>-3Y519kKzJw9vDA?C4IGbK^bXuD`m1vR*)UMw@yv`Pi#5_*H<*V&9T-vJk>;gG zob)UXr<&VP9GHMKhs1bVfrm=kAn%;a;f9LNQJfWj8yAd%ZA>s=`3oEd7g78fuBrD> zdD(GlOIMI(_cON%BID6+EaqflT?}7~afpwegJC^_keIrOy65sMNwwXCXD%Oxo{Zlx z#B|iDpp0CVU&J#|qjyJznro1A_2qJ~Q_kr(=pkdz<^LuK8mo14qx~)VzmWd}oqpOb TOgI^o00000NkvXXu0mjfLl)`& literal 0 HcmV?d00001 diff --git a/examples/app1/grails-app/assets/images/documentation.svg b/examples/app1/grails-app/assets/images/documentation.svg new file mode 100644 index 0000000..29bc9d5 --- /dev/null +++ b/examples/app1/grails-app/assets/images/documentation.svg @@ -0,0 +1,19 @@ + + + + + + + + + + + diff --git a/examples/app1/grails-app/assets/images/favicon.ico b/examples/app1/grails-app/assets/images/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..76e4b11feda94c87ef50f4350eceeb1506e31311 GIT binary patch literal 5558 zcmds5TWB0r7@oAD-jLoD5kymO6!D=TzFEzK#d}{Q^hKedq)oD$o!QjZ7iB>ZDuT5Z zQK*!*+QLGbCUe=S#R?S^iw{!UT3ecmm$p`8F14H8xg5XmOinU8o!!i8KrnDJXU{qR z_xt7+PjCE@Rlt(vwLV=FO+C0aWg*UiUYXHhK9 zn3Z+9<$b8z{)wCwjIy!MG0t(#d~iiDT6zyu?$2577Q^;F&)edh;RIub9e91iLa?!~ z+updG!y2xo3@}K`6Fsg z9&0H>Sz~U8^;XC|F);Ud`BFoqbL@^Hv6TmA~KYha=;`uIpVVF zcXrTw3i|(+gdz2yErx~LYx+G(Mm=+;`;cx27wXA!oY>@0GSR0#P;P@y9ZlMIjSW7= znOfd(G|r^mO!u`pWu*?@QF2AODKGteSOoH%WA=;kb04}DT-abv-J>w*f7EF%eu)hZ zaz)R9v8p@X$lUUQU_3yd%RPhc!-~JNU)*X~{_%RYkLphja!uz43Bi0Zja%byQ90G; zI-KJb!*(B6{MmkS8^+G2;U^9;$VvIm;^<=Lk~DjlvOGg4_`Q3ex=Qi)9GF-R-~S>l z2G^03+!@nb2i?!5(VcorS z`5OJsTJCDt>N9EAkgvwK$(gf!+~3LkJZI>4jCrjQPop_~f4L1gaX{+8`y*mo#ZMd1 z7E$|$XC3^QG2Qk?_-W=gGOs$`;hNY`_Q%;@<$rQcRI+BZHw}K8HJO2Od)^AphyD=8 zzw*C${B4r?JvfprPTrgbJIxy0jSF?t+kv|a`5pW+{=@jI%|D1UNBF2^UP*)7%o@XT zA7vcT7oz!x7%Klte|P-J+~CxjX0X$&eQ{9Sg1mW}9MnPOU&{7cynp-=+Mef4- zLUDXq&+dukT_24tUBBzkCA@EGt8@+bA!H~^90QH_CH(Nqmp-}r{^=+d7pnUgdBdYF zsDZvm?8uh4{geNsDS2Y$9si7Bi@)dt@%xuHn5jQt1KNmZG@ngk(zj9L#On|8^H0`a zPJ{X@l0TxkBu$_EIRCo!7x`r!RjOZqrr}9iBmJHAC$OV+4>{w1jMe1&JNYh6;+q+p zjy2Zu#3Xi{FX=bm6|Q>!Xwvp7&YFGyi0YH{{-toE_XYcmY3XCr@%?Y!zoI&zU-SNn z95#aceH1n>^A2N^eT>Pud3^tj>494DKY9O-tMR}3&F>`mbRxfyH2a;TdvNM*)KBjq zUysXX`#U%`mw{Nn>Lvd@`0XX+`|nU&z6<&7x+A_u{d$RWNi{BUyN9X|!naRu{!|Fw zB{{pgLGksPQ%^DmHXlN*g*9BO=J+Hgz7L$D9CHNkyS(Ea>L?yx#CgP23aUl?og~B!g%=2 zVLI1vo-&k$ZrhX`pUkP8$M_!B#xi%C?i(0$!+p)>OUfcnFDiLCZ;NC3A@A8Iwx7O+ z`tgtXv2V~_gEKf1#V~9i-+O#1PhRZvn~kV#rBK2@zK`i8F|07iO&vmei7VNm@*@di zHA9_dWhL~G-y$e0?PmuQ$fsY`ENt)t?>7C|^JU6%8TPOf_W1?xu5X9U-$v|sQ*-do zQm-|ufmy$AH<51n|(Hp aAB7=xL9F5|-{N;0+0aiDzfp{|{l5XfK*m4- literal 0 HcmV?d00001 diff --git a/examples/app1/grails-app/assets/images/grails-cupsonly-logo-white.svg b/examples/app1/grails-app/assets/images/grails-cupsonly-logo-white.svg new file mode 100644 index 0000000..d3fe882 --- /dev/null +++ b/examples/app1/grails-app/assets/images/grails-cupsonly-logo-white.svg @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/examples/app1/grails-app/assets/images/grails.svg b/examples/app1/grails-app/assets/images/grails.svg new file mode 100644 index 0000000..79f698b --- /dev/null +++ b/examples/app1/grails-app/assets/images/grails.svg @@ -0,0 +1,13 @@ + + + + grails + Created with Sketch. + + + + + + + + \ No newline at end of file diff --git a/examples/app1/grails-app/assets/images/skin/database_add.png b/examples/app1/grails-app/assets/images/skin/database_add.png new file mode 100644 index 0000000000000000000000000000000000000000..802bd6cde02d442288490c5f278b225e192927b5 GIT binary patch literal 658 zcmV;D0&V??P)$< zN?sD2BV4j9Yl`*+fsWQD?H_4>L?~r48B=l;Spkuc)A?yA6iP)R5d;DO`2BwH_g=3D z!!XcjG|+Ch%jCP5&1Rc|$N`LEvA9yN*EyX%X^loByIQT_h>#+l_9wi@{(Z?D2RE zUDq)j4#hY2{Z&BrR!Yn$4g z^!3C0RHpz#W@n--_jThHPEAe2R834DnuV#1kUlxXv}=C^n7~&>f1RO6h@8fUuT`wRE91*{Z%LW-oO8KckjTdf s77ewwyuEar+*b)ffn+a07*qoM6N<$f>LlT?EnA( literal 0 HcmV?d00001 diff --git a/examples/app1/grails-app/assets/images/skin/database_delete.png b/examples/app1/grails-app/assets/images/skin/database_delete.png new file mode 100644 index 0000000000000000000000000000000000000000..cce652e845cde732ac3ce9a4132b597301ad660e GIT binary patch literal 659 zcmV;E0&M+>P)ps_J1 zHhTmGl@tMUh=_=a3Fb%)BqZVTW3s!xH?UsxE?7A5@n+t<@6Gq#%m~l(@IOPFT;%il z03|#}Sa4nU2-$-Kn!4}FekQv@$S0FY$L9!N0g;c={9!m8K4zLG48uSu6aw$J+ii5a zT~sO+G#ZW9!I0fqFSvY9*@h|sR=YqL#x%oU@(yD@pz0* zr-R{eDEHX6V*RaK<|4rWl@GKt@8COeKZT>%ICBq4+h_I-+?Y*V28oxmqBc+Oz5 zc>4@kzJgDe<1lkKXYEtktsNC`FoTI)4qLaF!_1E&4qv>EKx`iUcee83)!MzalltZ# z3K)~8`*H_w9^uf5^9X)<39-6>(AOuJvu0IKc#FRkFoCa%ULtC>8v6bIR-H|{S~CWm zy|LB2yL+L!Vs5g8ONBz=aUzj0EX$JbfSV}a#vT*B_2)32Uc<0oLyzLS9V$=7hM4?~ znM@`|iEa~8)bZW?7q}d~l*7K(I`+@}grT|_=WaH**u tj~DMRZZpzVy^OjT85Vq(G=8XD@S91FH}kp`d7hyPh1 z5CCa%X>*RH50W(1GMNlqE*ChCgTvu4bCM)M5Co)BDTG2HvvyYjmSvI4<)A2v`CcxU zQ79BpEEdf*P56K_c;lUWy=bic9s#4IZm`yWps?HR<^;5uf_%3rLf1Uy7}ym7{u3SG zgQxL#;V@<+y-&7GK#MIB!!Xb^&5SuEhPoOV9{wDJpWonQN~qlHho`!h-y&cUDCjiw zot3{JSR;b3Z$U9V2&bDtVsaL$Qu?FFtIb;kXm<)qqyl<=9Kq@&_)r^^)N|OJWjH)_ za7i;6X_aj`duRB^h5&`toyKA^3V-E1_yb`=eg>PPj8Y+p^vFkpk)_tg?(s>=HO~R< zNVkfdL~{$}rBQI|9QHR{MrpYhcBg@2p$?h%pAnUsbBDUeKUv#oTc6-&EEZdf$Kwze zM&QwZLDd6D&pd?=1#1F{N2f6?Hf8gwvt`>|pf)ft5F|nm_AI~XywcT&?}K--v^WN? z_7vo-s3)9F{TXfF{hpqll^q2vdwBb}datvKg-yfc+gC^|#8-K5)%lB$rlxi}-rEGO xUZ|2A>wRp~(I5;*aZFyx-fDe3J-^%i_y?+(!m^mX(n$aS002ovPDHLkV1gwlT*CkW literal 0 HcmV?d00001 diff --git a/examples/app1/grails-app/assets/images/skin/database_save.png b/examples/app1/grails-app/assets/images/skin/database_save.png new file mode 100644 index 0000000000000000000000000000000000000000..44c06dddf19fbda14efe428b9b1793c13f46b2cf GIT binary patch literal 755 zcmV3^_07cLZBR}_>&jXObH zw2it@svr%qE?kJ(Xuudu+DSW|WWK!jNvbU^UO02#+Tt zYOko4%Vx8c4Gh!M(=Qem7g;XcE?n0Qi^XD?&*vX7@xPFCIh;%;@xMr?(;$(vo9j9i z6;riZMJyIWG#Z6r7^-I5HtO{{DwPWQ`}>&y+Y;!yjz*&a$8prX=XtO!3$0d5J>%Mz z1f8>Jnx-7^X2#7Yb#zC2VYfZ>c17@L{s)8{OuWBa3WHFfVXfhLv2t?V0V~q5R2D*D z&315l_#iF}b>Zoo?-;+7*`WOJWsMw(x3WXv`@U*s@Y-&edFEYpz0skP)dFfu zZ4wIp&Vbb!+|0+3Qa}p<*AH-eY>3q8s6?RA)zqP8W39IT5HLFG9m1F);gE|P`L7@@ zctjKsn1rA6!ZZR%R^(SjU!r=2o$yGp<$KViK~{B;AIcgvN+J+&Nvur+W(Sw&=H?z} zGMRW^U!Nl3AvWzQ3~C%Z*G*(?qLfNCq;tpg2yRW4@yl9;p3CK)O-@c8Sy))OUMiKc zQp#QYFZe-*@LZDInR^#F=Bm=!vA2i6tkEJ#i0aggzp2D%3!>h~r~3uLt(-IMoyFAT&uF!>{(iS?1OX-eX zKw9bunxR5FrF6QaYs~9>A4#zW^dwIvCpq(+cfR?U`T6-{9LHUqo16RKcDwUVr?cX4 zIN~hJDs48~aRAJ}U_2g=KAB9SP$;0;Y@*$6Ly{z<(`i^NmbL#1W@l#$wOS3;YPBOE zJ;7`?L*?Ga6XzC292wl75}>gDz`(>h?is$JPxm#0jGnotoK|nAVM5$DQ z!C*kO-aeF@+Ejy?nVHEp8V&F~k7BWicsx!aH9kHLRpcQ?L&JFBAB4i&kAaVUxVvzh z3a-EY0%m%8nhI7|SE(QpiBL#sG#VUMM9}*(0mg2(Q$Zq;z|PJNd_Euiem@;jtJN@i za|c2MmsL?PR;yKNwOUA}QuO=7;V@#c7!{~gs?J7hAlsE7U#g?$aRkhSTqLq6iuCu9 z10_j_=;?Dc?4cZ386qH0HkgHTDT|HmGR`W4V2noNQJqfLJEot)q{V_UtsW+m31cP~ zDwWEi3HYBSoF4M;T?VaIdqinn1HZ9}32qs-PdwPbCf+WI6n9jl0-8cjV3%1FB%B&r z+`mzSliyLSH0dxYE}rk&=!uCa*V>()2znj`_XYjtbt>@4FLHnJE|G`xv)Ba@oLBny z1%3K7c4fiB^4{k6E8Pif0kNy62}b@9+N#0$9Ug7g~-`rQ^qx~m@y2OU8A z#zh~=7n#Z$Z*fx-GOtDf07cgx0suCz_W(2~Y(0tf@FX@P6EPuM_dgn$vj9LucO)%W zw%HgMW>=#oL>nZ>M&NEf08>)#)k<{$fCT_r>rPi=BV=hFh6WS^qqze>C6Ek}o{M5% za|@JGowu0t{&hgNzySHZxy@LTNh);YzZ2zSp_ zl$^T&Dnc|NLb&RD_!4>pt@VHdP)ZGER%5ZmWEe$lryR&y;2u^3cOkO4#6c%-(EY6a{600000NkvXXu0mjfxS2AI literal 0 HcmV?d00001 diff --git a/examples/app1/grails-app/assets/images/skin/house.png b/examples/app1/grails-app/assets/images/skin/house.png new file mode 100644 index 0000000000000000000000000000000000000000..fed62219f57cdfb854782dbadf5123c44d056bd4 GIT binary patch literal 806 zcmV+>1KIqEP)v;U&v3%|^C`Ga3?LtY&4dQB4Oz;1v;J%z!D&%WRH@BZ?x; z3)8@IUIv@hG|@IwyHLC`l{1<4BK>wam95g|i|?Cfzt876&-Zx_0f5*l-9`IJI&mHu zE6$@xB)6N}7VeR;!X8D!TAw;;&0Bsj?A071cO>X3K0wl7WZ1;Tg!4LHyNcnzoeQ7t zNW`aSlm8WXYkek&ir$13=ngczvf zV0vnjNpCF&K8px}dunv+`LIb-sOC$_jD(;IBI$xC|7`(+9cA>Vir_V#z{?k7SX^Ah z^71m~W@q439Ycqfhi7+gp#A14n1n1!e>$EdeATG|f798Y=ggzwEKH2Q!qU2QA(Se?dwqG69%>n$6rtE z%F(845Az8c{w(XgimJg96!jLMz?zS6I1HUm2baqQx7&@nx;lhHA!r6vs2|fqJETOu zLxeu2OQ(3(au%dg>AcZsWI(zXn9XJg1cLe8k~0h0wOL=&HK}7X k{AKr*U4z7Szv)i%9gTgghwgU$Q~&?~07*qoM6N<$g31kYk^lez literal 0 HcmV?d00001 diff --git a/examples/app1/grails-app/assets/images/skin/information.png b/examples/app1/grails-app/assets/images/skin/information.png new file mode 100644 index 0000000000000000000000000000000000000000..12cd1aef900803abba99b26920337ec01ad5c267 GIT binary patch literal 778 zcmV+l1NHogP)BVme|mWaqy4$_pJm?y9KM{-*hp?1+Ey3e-CEDooTa!B;e(Q>TSF?bj>5At13y1p zriN3w3x~5SfZj{@J4M{kp{?=M_Lh2bV+5LH)Q)5W!-ePA$RgE1@5f1cyHki0Y}JyVEYZF(LD$xXlt$7A5CgE@ zpV-&l%vf;=5kZ2-2gi@Y6J&=cuwt>!vJ^#(&n|LcZyUzi6Duj$$hJ1s*HD-#;k-w@ zpdrwAuoDG_N2bvb07G$Zk*?Hc)JLtW4yqOnic_$zO7NZ#l>Fm){;fE?b$IbOaX2fe z0la4g0Dfw2xk7Wi7NapVD8YMPCZu?A1QCK*67dgsvRKBLFtrM>?$%&_lD1882mzdO zWPdw5KWw6IT`m1b_8=lS5jt8D3=RDa=&jWzR-)S@56WMslZ~mKu1)-wpXB>rNBQ>N zU#K`#1B&v|_AQK;7I~B}OdGiUT9LX>f0xm6<;LeP!=vFjPsUQF*wCJ*dO)4YBypgdiuF!=i@6Zyi7F|q#K zz?tlSZULa@t1D?$e;f@b36&N!V2mjOHw|*FMR=dr@6o0ZXGBB_+=zx3%$`cG63Jm-*84Da1I50Ew7%?y?G#+5$ UVU>wEFhP-tNtBU=gM+~u00(^_>i_@% literal 0 HcmV?d00001 diff --git a/examples/app1/grails-app/assets/images/skin/sorted_desc.gif b/examples/app1/grails-app/assets/images/skin/sorted_desc.gif new file mode 100644 index 0000000000000000000000000000000000000000..38b3a01d078418d3afcdb2765251a9f21b7995be GIT binary patch literal 834 zcmZ?wbhEHbWMg1s_|Cu}C@83_tE;D{=jG+)?d|RKCt}{bc?%XSShQ%-?%lih?%lh8 z|9*y1Fd72GGz1iXvM@3*urla?{0GVt3>@+doD32i4hNW;7z9Kl3=A9^nYh^GDt25@ NJj%i*$Hu~74FL5|8=3$B literal 0 HcmV?d00001 diff --git a/examples/app1/grails-app/assets/images/slack.svg b/examples/app1/grails-app/assets/images/slack.svg new file mode 100644 index 0000000..34fcf4c --- /dev/null +++ b/examples/app1/grails-app/assets/images/slack.svg @@ -0,0 +1,18 @@ + + + + slack_orange + Created with Sketch. + + + + + + + + + + + + + \ No newline at end of file diff --git a/examples/app1/grails-app/assets/images/spinner.gif b/examples/app1/grails-app/assets/images/spinner.gif new file mode 100644 index 0000000000000000000000000000000000000000..1ed786f2ece49ec5db07dee13a56ef38025b628c GIT binary patch literal 2037 zcmY*ZcTf|19{+AO8xjIZfItFCFrkL3BodGwflvety+|>T03uy{D35o<4X`9Q3=bSU zojZMs3Qw_)j=f_!)B!!mUKqwc>ezd^4c;H{$Ifr|uTTHRC8&buXgI)uM*u&6{`~Rd zM}L3+_wV1IK7G1x-@ebEKY#i1<^B8j-@bj@wr$&s7cWknII(;8?sMnPJ$(4^-Me>t z_wN1p@#D#pCr3v|A3b`6qUeJM5ANT;KRi7A^5x65YuBDSb?WNXs}mCwVPRo|gM+(v z?RxX(&Gzlv_w3no`}XavTesf4dGq@9>xT~?_VDnybm`KK8#fLfJh*P%x(gRB?AWp6 z>({T(pFdAaOMCY0SzBA%hYueT6BF;;xnpHzb@}q;O`A4(d3im4{J5Z?;ONn#0|NsW zFJ9cgfB);(ugAv5a&vP_OG`&aMz(C(^6J&A@$vBk2M!!Re*DRkCvV@rJ%9duQ&ZFC z&6|%MJC>4?a{Bb?vuDqqIdg`~z*4Ws1>(;I2=4ORL zQCC-|R4OYgD_5;rwQSk44I4IW+_=%t&(GD>Rj=1yxpHM_Xh`ytnG&0k9<5Zz%KT@c z2mnYvQ>hsF`jQ_R5(mKIydH2saldkg!Gzlvi9nFeu<gyfnCs8|SGO1R0M3N~Sp zx@MoynP5Rh(303ldPHFemMT%$+lXy}A1JR?IwL6=E`apW=@Z-0R!QO^@j_&{6#;i|5R1o$ zJ1J~xCDQ$1cs9`DW9Z!)D)^+%&Ug81908UkMXX;jkp>#b+SE}d{`0S>>Ef(`Ns6oa zB~H-LX25y1!BDgW%?nC0$RettYz8zsuz>9Z!g8TH$kU;rwYWrSTkjuYCH9utgFU6b z*wzBKaG6IjEXYSpAo5j4&c(t zwi-c(Q6YX2N-v2q(mgN`>wuCTV&XSRUA4h-f8g+uV2k_=o;2wb`7N)&+7T-C+CT_Bu4KrjWmK>x0AbH2rmR&7+q6fX+R0o&}V!t?TP+g0{x`8PSP{7CvnEPL^Hzx^T2Eus2 zi$U6ZM7}+l%$}afWn#%|+MPTsC236GKi9-ad*Z9;Ymg?jSO$L1s$w4BvDzu_kH9>z zTWC{K?jx4~H3oGGt3}I}LWNw@H)C>9GF4^|x(5n#+Va}kr_cb>{a+K%>B+@JNrC8? zJOUkD3fe*NdA-rJupqo?FfRK}k zOm!l7Dj$dsL|kS`3FL3^@^7axrU9d*@79yf!+bu3kXbziU!v&TO3p#w{y_lYG5ZPzDh;giB_lkGp((nqZ12&%LTrgm4vY= z{ffd0B$DiUKReJ9>?{#_KVrMyhiu_A8fNd!&DWTZ4+9S|1lJHkLv-^}a0IC{WI9N! zQTY-}db!%OH^;l2ZeajnA&Q6Uv=#0KZB9;^^lzMOi^0~bS~m!&K#e6hia79(ItV9M SXP~*+AU!zMlD%~Wg#Hic*k=_0 literal 0 HcmV?d00001 diff --git a/examples/app1/grails-app/assets/javascripts/application.js b/examples/app1/grails-app/assets/javascripts/application.js new file mode 100644 index 0000000..acc8069 --- /dev/null +++ b/examples/app1/grails-app/assets/javascripts/application.js @@ -0,0 +1,29 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +// This is a manifest file that'll be compiled into application.js. +// +// Any JavaScript file within this directory can be referenced here using a relative path. +// +// You're free to add application-wide JavaScript to this file, but it's generally better +// to create separate JavaScript files as needed. +// +//= require webjars/jquery/3.7.1/dist/jquery.js +//= require webjars/bootstrap/5.3.7/dist/js/bootstrap.bundle +//= require_self diff --git a/examples/app1/grails-app/assets/stylesheets/application.css b/examples/app1/grails-app/assets/stylesheets/application.css new file mode 100644 index 0000000..a68aa0b --- /dev/null +++ b/examples/app1/grails-app/assets/stylesheets/application.css @@ -0,0 +1,34 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +/* +* This is a manifest file that'll be compiled into application.css, which will include all the files +* listed below. +* +* Any CSS file within this directory can be referenced here using a relative path. +* +* You're free to add application-wide styles to this file and they'll appear at the top of the +* compiled file, but it's generally better to create a new file per style scope. +* +*= require webjars/bootstrap/5.3.7/dist/css/bootstrap +*= require grails +*= require main +*= require mobile +*= require_self +*/ diff --git a/examples/app1/grails-app/assets/stylesheets/errors.css b/examples/app1/grails-app/assets/stylesheets/errors.css new file mode 100644 index 0000000..6296629 --- /dev/null +++ b/examples/app1/grails-app/assets/stylesheets/errors.css @@ -0,0 +1,117 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +h1 { + font-size: 2rem; +} + +h2 { + font-size: 1.25rem; +} + +.filename { + font-style: italic; +} + +.exceptionMessage { + margin: 10px; + border: 1px solid #000; + padding: 5px; + background-color: #E9E9E9; +} + +.stack, +.snippet { + margin: 10px 0; +} + +.stack, +.snippet { + border: 1px solid #ccc; +} + +/* error details */ +.error-details { + border: 1px solid #FFAAAA; + background-color:#FFF3F3; + line-height: 1.5; + overflow: hidden; + padding: 10px 0 5px 25px; +} + +.error-details dt { + clear: left; + float: left; + font-weight: bold; + margin-right: 5px; +} + +.error-details dt:after { + content: ":"; +} + +.error-details dd { + display: block; +} + +/* stack trace */ +.stack { + padding: 5px; + overflow: auto; + height: 300px; +} + +/* code snippet */ +.snippet { + background-color: #fff; + font-family: monospace; +} + +.snippet .line { + display: block; +} + +.snippet .lineNumber { + background-color: #ddd; + color: #999; + display: inline-block; + margin-right: 5px; + padding: 0 3px; + text-align: right; + width: 3em; +} + +.snippet .error { + background-color: #fff3f3; + font-weight: bold; +} + +.snippet .error .lineNumber { + background-color: #faa; + color: #333; + font-weight: bold; +} + +.snippet .line:first-child .lineNumber { + padding-top: 5px; +} + +.snippet .line:last-child .lineNumber { + padding-bottom: 5px; +} \ No newline at end of file diff --git a/examples/app1/grails-app/assets/stylesheets/grails.css b/examples/app1/grails-app/assets/stylesheets/grails.css new file mode 100644 index 0000000..4ba5cc0 --- /dev/null +++ b/examples/app1/grails-app/assets/stylesheets/grails.css @@ -0,0 +1,1071 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +html, code, kbd, pre, samp { + -ms-text-size-adjust: 100%; + -webkit-text-size-adjust: 100%; +} + +html, body { + height: 100%; + -webkit-overflow-scrolling: touch; +} + +p, ul, pre, h1, h2, h3, h4, h5, h6, h7, h8 { + margin: 1em 0; +} + +p { + display: block; +} + +h1, h2, h3, h4, h5, h6, h7, h8 { + font-weight: bold; +} + +pre { + border-radius: 0; + border: 0; + font-size: 14px; +} + +/* customizing bootstrap nav bar */ +.navbar { + margin-bottom: 0px; +} + +.navbar-dark a { + color: #ffffff !important; + font-size: 18px !important; + text-decoration: none; +} +.grails-icon img { + width: 40px; + +} +.navbar-dark, .navbar-static-top { + background-color: #424649; + border: 0px; +} +a.navbar-brand { + color: white !important; + font-size: 19px !important; +} +.navbar-dark .navbar-nav>.active>a, .navbar-dark .navbar-nav>.active>a:hover, .navbar-dark .navbar-nav>.active>a:focus { + background-color: transparent; + color: white; +} +.navbar-nav>li.active>a { + color: white !important; +} +.navbar-nav>li>a:hover { + background-color: #2559a7 !important; + color: white !important; +} +.navbar-nav>li>a { + color: #c0d3db; +} +.navbar-dark .navbar-toggler .icon-bar { + background-color: white; +} +.navbar-dark .navbar-toggle:hover, .navbar-dark .navbar-toggle:focus { + background-color: #2559a7; +} + +.navbar-toggler { + position: relative; + float: right; + padding: 9px 10px; + margin-top: 8px; + margin-right: 15px; + margin-bottom: 8px; + background-color: transparent; + background-image: none; + border: 1px solid transparent; + border-radius: 4px; +} + +.nav .dropdown a.dropdown-toggle { + padding-top: 25px; + padding-bottom: 25px; +} + +@media (min-width: 768px) { + .container { + width: auto; + } +} + +/* specific to index.html */ + +@media (max-width: 999px) { + #fork-me { + display: none; + } + + .navbar { + padding-right: 0px; + } +} + +#fork-me{ + position: fixed; + padding: 0px 50px 0px 50px; + top: 40px; + right: -60px; + background-color: #a60000; + color: #ffffff; + font-size: 1em; + z-index: 100; + transform: rotate(+45deg); + text-align: center; + font-weight: bolder; + border: #c14646; + border-style: dashed; + border-width: 1px; +} + +#fork-me p { + margin: 0em 0; +} + +#band { + /*grey =#808080*/ + background: #2559a7 no-repeat 50% 30%; + height: 400px; +} + +.svg #band { + background-image: url(../img/grails-cupsonly-logo-white.svg); +} + +.no-svg #band { + background-image: url(../img/groovy-logo-white.png); +} + +@media (max-width: 1010px) { + #band { + background-size: 90%; + height: 300px; + } +} + +@media (max-width: 690px) { + #band { + background-size: 80%; + height: 200px; + } +} + +@media (max-width: 475px) { + #band { + background-size: 70%; + height: 100px; + } +} + +#they-use-groovy { + width: 100%; + height: 450px; + background-color: #db4800; + margin-bottom: 20px; + text-align: center; +} + +#they-use-groovy .item { + text-align: center; + color: white; +} + +#logos-holder { + display: inline-block; + padding: 0px; + margin: 0px; + text-align: center; +} + +#logos-holder .logo { + padding: 0px; + margin: 0px; + display: inline-block; + width: 100px; + height: 80px; + background-size: 95%; + background-repeat: no-repeat; + background-position: 50% 50%; +} + +@media (min-width: 330px) { + #logos-holder { + width: 320px; + } + + #they-use-groovy { + height: 1130px; + } +} + +@media (min-width: 475px) { + #logos-holder { + width: 420px; + } + + #they-use-groovy { + height: 900px; + } +} + +@media (min-width: 690px) { + #logos-holder { + width: 630px; + } + + #they-use-groovy { + height: 600px; + } +} + +@media (min-width: 1010px) { + #logos-holder { + width: 940px; + } + + #they-use-groovy { + height: 450px; + } +} + +.centered { + text-align: center; +} + +.event-img { + margin: -20px -20px 20px -20px; + background-repeat: no-repeat; + background-position: 50% top; + height: 180px; +} + +.event-logo { + height: 180px; + float: right; +} + +@media (max-width: 1010px) { + .event-logo { + height: 100px; + } +} + +@media (max-width: 690px) { + .event-logo { + height: 60px; + } +} + +@media (max-width: 475px) { + .event-logo { + display: none; + } +} + +article .content time { + font-weight: bold; +} + +.doc-embed { + border: 0; + width: 100%; + min-height: 100%; +} + +.download-table { + width: 100%; + text-align: center; +} + +.download-table td { + width: 20%; +} + +#mc-embedded-subscribe { + width: 200px; + font-weight: bold; +} + +#mc-embedded-subscribe:hover { + background-color: #F2F2F2; + font-weight: bold; +} + +#footer .colset-3-footer .col-1 h1, #footer .colset-3-footer .col-2 h1, #footer .colset-3-footer .col-3 h1 { + font-size: 15px !important; +} + +.anchor-link:before { + content: ' # '; + color: lightgray; +} + +.anchor-link:hover:before { + color: orange; +} + +code, kbd, pre, samp { + font-family: "Source Code Pro", "Consolas", "Monaco", "Bitstream Vera Sans Mono", "Courier New", Courier, monospace; +} + +#contribute-btn { + position: absolute; + right: 15px; +} + +@media (max-width: 767px) { + #contribute-btn { + width: 100%; + position: relative; + margin-top: 30px; + right: 0px; + } + + #contribute-btn button { + width: 100%; + right: 15px; + } +} + +@media (min-width: 1200px) { + #contribute-btn { + top: 25px; + right: 15px; + } +} + +#big-download-button { + float: right; + font-size: 30px; + padding: 15px; + margin: 10px 0px 10px 20px; + border: 2px solid #db4800; + border-radius: 6px; + background-color: #db4800; + color: white; +} + +#big-download-button:hover { + background-color: #e6e6e6; + color: #db4800; +} + +.colset-3-footer .col-1, .colset-3-footer .col-2, .colset-3-footer .col-3 { + min-width: 180px; + float: left; +} + +.colset-3-footer .col-3 { + min-width: 220px; +} + +.colset-3-article article { + float: left; +} + +.col1, .col2 { + min-width: 300px; + float: left; +} + +@media (max-width: 988px) { + .col1, .col2 { + width: 98% !important; + max-width: 98%; + } + + .colset-3-article article { + width: 98% !important; + max-width: 98%; + } +} + +body, html { + font-family: "Open Sans", "Helvetica Neue", Helvetica, Arial, sans-serif; + padding: 0; + margin: 0; + background: #FFF; + color: #343437; + line-height: 25px; + font-weight: normal; + font-size: 14px; +} + +a { + color: #2559a7; + text-decoration: underline; +} + +a:hover { + color: #2559a7; + text-decoration: none +} + +h1 { + font-size: 2.125em; + margin: .67em 0 +} + +h2 { + font-size: 1.6875em; + font-weight: bold; +} + +h3, #toctitle, .sidebarblock > .content > .title { + font-size: 1.375em; + font-weight: bold; +} + +h4 { + font-size: 1.125em; + font-weight: bold; +} + +h5 { + font-size: 1.125em; + font-weight: bold; + color: #2559a7; +} + +h6 { + font-size: 1.08em; + font-weight: normal; + color: #2559a7; +} + +h7 { + font-weight: bold; + color: #245f78; +} + +h8 { + color: #245f78; +} + +#footer { + background: #f2f2f2; + text-align: center; + font-size: 14px; + padding: 20px 0 30px; + margin-top: 30px; + color: #AAA +} + +#footer .col-right { + float: right; + width: 300px; + text-align: right; + padding-top: 10px +} + +#footer .colset-3-footer { + color: #222; + font-size: 14px +} + +#footer .colset-3-footer:before, #footer .colset-3-footer:after { + content: " "; + display: table +} + +#footer .colset-3-footer:after { + clear: both +} + +#footer .colset-3-footer .col-1, #footer .colset-3-footer .col-2, #footer .colset-3-footer .col-3 { + width: 18%; + padding: 20px 0 30px; + padding-right: 3%; + float: left; + text-align: left +} + +#footer .colset-3-footer .col-3 { + width: 24%; +} + +#footer .colset-3-footer .col-1 h1, #footer .colset-3-footer .col-2 h1, #footer .colset-3-footer .col-3 h1 { + font-weight: 600; + font-size: 15px; + line-height: 30px; + margin: 0 +} + +#footer .colset-3-footer .col-1 ul, #footer .colset-3-footer .col-2 ul, #footer .colset-3-footer .col-3 ul { + list-style-type: none; + margin: 0; + padding: 0 +} + +#footer .colset-3-footer .col-1 ul li, #footer .colset-3-footer .col-2 ul li, #footer .colset-3-footer .col-3 ul li { + margin: 0; + padding: 0 +} + +#footer .colset-3-footer .col-1 ul li a, #footer .colset-3-footer .col-2 ul li a, #footer .colset-3-footer .col-3 ul li a { + color: #343437; + text-decoration: none +} + +#footer .colset-3-footer .col-1 ul li a:hover, #footer .colset-3-footer .col-2 ul li a:hover, #footer .colset-3-footer .col-3 ul li a:hover { + text-decoration: underline +} + +#footer .second a { + color: #db4800 +} + +.band { + background: #4298b8; + height: 400px; + margin-bottom: 20px; + color: white +} + +.band .item { + text-align: center +} + +.band .item:before, .band .item:after { + content: " "; + display: table +} + +.band .item:after { + clear: both +} + +#content { + margin: 2em 0; + padding: 1em 0; + background: white; +} + +#content .row > h1 { + font-size: 34px; + line-height: 40px; + font-weight: 200; + text-align: center; + margin: 0; + padding: 20px 0; + width: 100%; +} + +#content hr.divider { + border: 0 none; + border-top: 1px solid #EEE; + margin: 0 5%; + margin-top: 40px +} + +#content hr.divider { + margin: 0; + margin-top: 40px; + margin-bottom: 30px +} + +#content .colset-2-its:before, #content .colset-2-its:after { + content: " "; + display: table +} + +#content .colset-2-its:after { + clear: both +} + +#content .colset-2-its > h1 { + padding-bottom: 15px; + margin-top: 15px; + margin-bottom: 0 +} + +#content .colset-2-its > p { + margin-top: 0; + padding-bottom: 5px; + text-align: center; + color: #222; + font-size: 15px +} + +#content .colset-2-its .col1, #content .colset-2-its .col2 { + float: left; + width: 48%; + padding-right: 1%; + padding-left: 1%; +} + +#content .colset-2-its .col2 { + padding-left: 1%; + padding-right: 1%; +} + +#content .colset-2-its article { + padding: 10px 0 +} + +#content .colset-2-its article:before, #content .colset-2-its article:after { + content: " "; + display: table +} + +#content .colset-2-its article:after { + clear: both +} + +#content .colset-2-its article .icon { + display: block; + width: 80px; + height: 80px; + background-image: url(../images/icons-colset-2-its.png); + float: left; + margin-top: 12px; + margin-right: 15px +} + +#content .colset-2-its article .icon.icon-1 { + background-position: 0 0 +} + +#content .colset-2-its article .icon.icon-2 { + background-position: 0 -80px +} + +#content .colset-2-its article .icon.icon-3 { + background-position: 0 -160px +} + +#content .colset-2-its article .icon.icon-4 { + background-position: 0 -240px +} + +#content .colset-2-its article .icon.icon-5 { + background-position: 0 -320px +} + +#content .colset-2-its article .icon.icon-6 { + background-position: 0 -400px +} + +#content .colset-2-its article > h1 { + font-size: 19px; + font-weight: 600; + margin-bottom: 0; + line-height: 30px +} + +#content .colset-2-its article p { + margin: 0; + line-height: 24px; + font-size: 14px +} + +#content .first-event-row { + padding-top: 30px; +} + +#content .last-event-row { + padding-bottom: 30px +} + +#content .colset-3-article > h1 { + font-size: 24px +} + +#content .colset-3-article div.content { + padding: 20px; + padding-bottom: 5px +} + +#content .colset-3-article article { + float: left; + width: 29%; + margin: 10px 2%; + -webkit-box-shadow: inset 0 0 0 1px rgba(0, 0, 0, 0.1); + box-shadow: inset 0 0 0 1px rgba(0, 0, 0, 0.1) +} + +#content .colset-3-article article .img { + margin: -20px -20px 20px -20px; + background-position: center top; + height: 180px +} + +#content .colset-3-article article h1 { + margin: 0; + font-size: 18px; + font-weight: normal; + line-height: 25px +} + +#content .colset-3-article article h1 a { + color: #343437; + cursor: pointer +} + +#content .colset-3-article article h1 a:hover { + color: #46a5c8 +} + +#content .colset-3-article article p, #content .colset-3-article article time { + font-size: 13px +} + +#content .colset-3-article article .author a { + color: #db4800 +} + +#content .colset-3-article article:first-child { + padding-left: 0 +} + +#content .colset-3-article article:last-child { + padding-right: 0 +} + +#content.page-1 .row { + padding-top: 10px; + padding-bottom: 10px +} + +#content.page-1 .row h1 { + text-align: left; + font-size: 36px +} + +#content.page-1 .row article { + font-size: 14px +} + +#content.page-1 .row article .desc { + font-size: 16px +} + +#content.page-1 .row article h1 { + margin: 0; + padding: 0; + text-align: left; + font-size: 26px +} + +#content.page-1 .row article h2 { + margin: 0; + padding: 0 +} + +#content.page-1 .row article h3 { + font-weight: bold +} + +#content.page-1 .row article pre { + display: block; + background: #f2f2f2; + padding: 12px 20px +} + +ul.nav-sidebar { + margin: 0; + margin-top: 20px; + padding: 5px 0; + border: 1px solid #EEE; + list-style-type: none +} + +ul.nav-sidebar li a { + display: block; + cursor: pointer; + padding: 5px 10px; + font-weight: 400; + text-decoration: none; + color: #343437 +} + +ul.nav-sidebar li.active a:hover, ul.nav-sidebar li a:hover { + color: white; + background-color: #db4800; +} + +ul.nav-sidebar li.active a { + background-color: #f2f2f2 +} + +.table { + margin: 20px 0 +} + +.table thead tr th { + padding: 10px; + font-weight: normal; + font-size: 18px +} + +.table tbody tr td { + vertical-align: top; + font-size: 12px; + padding: 10px; + border-top: 1px solid #EEE +} + +*, *:after, *::before { + -moz-box-sizing: border-box; + box-sizing: border-box +} + +body { + background: #444 +} + +html.noScroll { + overflow: hidden +} + +html.noScroll body, html.noScroll .st-container, html.noScroll .st-pusher, html.noScroll .st-content { + overflow: hidden +} + +html, body, .st-container, .st-pusher, .st-content { + overflow: auto +} + +.sign-in-fa-icon:before { + font-family: FontAwesome; + content: '\f090'; + padding-right: 10px; +} + +#st-container { + height: 100%; +} + +.st-content { + background: white +} + +.st-content, .st-content-inner { + position: relative; + height: 100%; +} + +.st-container { + position: relative; + overflow: hidden +} + +.st-pusher { + position: relative; + left: 0; + z-index: 99; + height: 100%; + -webkit-transition: -webkit-transform .5s; + transition: transform .5s +} + +.st-pusher::after { + position: absolute; + top: 0; + right: 0; + width: 0; + height: 0; + background: rgba(0, 0, 0, 0.3); + content: ''; + opacity: 0; + -webkit-transition: opacity .5s, width .1s .5s, height .1s .5s; + transition: opacity .5s, width .1s .5s, height .1s .5s +} + +.st-menu-open .st-pusher::after { + width: 100%; + height: 100%; + opacity: 1; + -webkit-transition: opacity .5s; + transition: opacity .5s +} + +.st-menu { + position: fixed; + top: 0; + left: auto; + z-index: 100; + visibility: hidden; + width: 300px; + height: 100%; + background: #2559a7; + -webkit-transition: all .5s; + transition: all .5s; + right: -600px +} + +.st-menu::after { + position: absolute; + top: 0; + right: 0; + width: 100%; + height: 100%; + background: rgba(0, 0, 0, 0.2); + content: ''; + opacity: 1; + -webkit-transition: opacity .5s; + transition: opacity .5s +} + +.st-menu-open .st-menu::after { + width: 0; + height: 0; + opacity: 0; + -webkit-transition: opacity .5s, width .1s .5s, height .1s .5s; + transition: opacity .5s, width .1s .5s, height .1s .5s +} + +.st-menu ul { + margin: 0; + padding: 0; + list-style: none +} + +.st-menu h2 { + margin: 0; + padding: 1em; + color: white; + text-shadow: 0 0 1px rgba(0, 0, 0, 0.1); + font-weight: 300; + font-size: 2em +} + +.st-menu ul li { + display: block +} + +.st-menu ul li a { + display: block; + position: relative; + padding: 1em 1em 1em 45px; + outline: 0; + box-shadow: inset 0 -1px rgba(0, 0, 0, 0.2); + color: #f3efe0; + text-shadow: 0 0 1px rgba(255, 255, 255, 0.1); + letter-spacing: 1px; + font-weight: 400; + text-decoration: none +} + +.st-menu ul li a span.fa { + display: block; + position: absolute; + left: 12px; + top: 17px; + font-size: 20px; + width: 30px; + text-align: center +} + +.st-menu ul li a span.fa.fa-tasks, .st-menu ul li a span.fa.fa-envelope { + top: 18px; + font-size: 18px +} + +.st-menu ul li:first-child a { + box-shadow: inset 0 -1px rgba(0, 0, 0, 0.2), inset 0 1px rgba(0, 0, 0, 0.2) +} + +.st-menu ul li a:hover { + background: rgba(0, 0, 0, 0.2); + box-shadow: inset 0 -1px rgba(0, 0, 0, 0); + color: #fff +} + +.st-effect-9.st-container { + -webkit-perspective: 10000px; + perspective: 10000px +} + +.st-effect-9 .st-pusher { + -webkit-transform-style: preserve-3d; + transform-style: preserve-3d +} + +.st-effect-9.st-menu-open .st-pusher { + -webkit-transform: translate3d(0, 0, -300px); + transform: translate3d(0, 0, -300px) +} + +.st-effect-9.st-menu { + right: -600px; + opacity: 1; + -webkit-transform: translate3d(-100%, 0, 0); + transform: translate3d(-100%, 0, 0) +} + +.st-effect-9.st-menu-open .st-effect-9.st-menu { + visibility: visible; + right: -300px +} + +.st-effect-9.st-menu::after { + display: none +} + +/* Video from the learn page */ +.presentations { + margin-top: 30px; + margin-bottom: 30px; +} + +.presentations img.screenshot { + float: left; + margin-right: 40px; + margin-top: 1em; + margin-bottom: 0px; + width: 300px; + height: auto; +} + +.presentations .metadata { + display: table-cell; + min-width: 328px; +} + +.presentations .title { + margin-top: 1em !important; + margin-bottom: 0.5em !important; +} + + +.presentations .speaker { + color: #245f78; + margin-bottom: 0.5em; +} + +.presentations .summary { + line-height: 1.3; +} + +.presentations .urls { +} + +@media screen and (max-width: 767px) { + .presentations .img.screenshot, .video .metadata { + float: none; + } +} diff --git a/examples/app1/grails-app/assets/stylesheets/main.css b/examples/app1/grails-app/assets/stylesheets/main.css new file mode 100644 index 0000000..8b6bd16 --- /dev/null +++ b/examples/app1/grails-app/assets/stylesheets/main.css @@ -0,0 +1,608 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +/* FONT STACK */ +body, +input, select, textarea { + font-family: "Open Sans", "HelveticaNeue-Light", "Helvetica Neue Light", "Helvetica Neue", Helvetica, Arial, "Lucida Grande", sans-serif; +} + +h1, h2, h3, h4, h5, h6 { + line-height: 1.1; +} + +/* BASE LAYOUT */ + +html { + background-color: #ddd; + background-image: -moz-linear-gradient(center top, #aaa, #ddd); + background-image: -webkit-gradient(linear, left top, left bottom, color-stop(0, #aaa), color-stop(1, #ddd)); + background-image: linear-gradient(to bottom, #aaa, #ddd); + filter: progid:DXImageTransform.Microsoft.gradient(startColorStr = '#aaaaaa', EndColorStr = '#dddddd'); + background-repeat: no-repeat; + height: 100%; + /* change the box model to exclude the padding from the calculation of 100% height (IE8+) */ + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; +} + +html.no-cssgradients { + background-color: #aaa; +} + +html * { + margin: 0; +} + +body { + background-color: #F5F5F5; + color: #333333; + overflow-x: hidden; /* prevents box-shadow causing a horizontal scrollbar in firefox when viewport < 960px wide */ + -moz-box-shadow: 0 0 0.3em #424649; + -webkit-box-shadow: 0 0 0.3em #424649; + box-shadow: 0 0 0.3em #424649; +} + +#grailsLogo { + background-color: #feb672; +} + +a:hover, a:active { + outline: none; /* prevents outline in webkit on active links but retains it for tab focus */ +} + +h1, h2, h3 { + font-weight: normal; + font-size: 1.25em; + margin: 0.8em 0 0.3em 0; +} + +ul { + padding: 0; +} + +img { + border: 0; +} + +/* GENERAL */ + +#grailsLogo a { + display: inline-block; + margin: 1em; +} + +.content { +} + +.content h1 { + border-bottom: 1px solid #CCCCCC; + padding: 0 0 1em 0; +} + +.scaffold-list h1 { + border: none; +} + +.footer img { + height: 80px; + margin-right: 25px; + margin-bottom: 15px; + clear: bottom; +} + +.footer strong a { + color: white; + text-decoration: none; + font-size: 1.1rem; +} + +.footer { + background: #424649; + color: #ffffff; + clear: both; + font-size: 1em; + padding: 2em 0; + min-height: 1em; +} + +.footer a { + color: #feb672; +} + +.spinner { + background: url(../images/spinner.gif) 50% 50% no-repeat transparent; + height: 16px; + width: 16px; + padding: 0.5em; + position: absolute; + right: 0; + top: 0; + text-indent: -9999px; +} + +/* NAVIGATION MENU */ + +.nav { + zoom: 1; +} + +.nav ul { + overflow: hidden; + padding-left: 0; + zoom: 1; +} + +.nav li { + display: block; + float: left; + list-style-type: none; + margin-right: 0.5em; + padding: 0; +} + +.nav a { + color: #666666; + display: block; + padding: 0.25em 0.7em; + text-decoration: none; + -moz-border-radius: 0.3em; + -webkit-border-radius: 0.3em; + border-radius: 0.3em; +} + +.nav li.dropdown-item a { + -webkit-border-radius: 0; + -moz-border-radius: 0; + border-radius: 0; +} + +.nav a:active, .nav a:visited { + color: #666666; +} + +.nav a:focus, .nav a:hover { + background-color: #999999; + color: #ffffff; + outline: none; + text-shadow: 1px 1px 1px rgba(0, 0, 0, 0.8); +} + +.no-borderradius .nav a:focus, .no-borderradius .nav a:hover { + background-color: transparent; + color: #444444; + text-decoration: underline; +} + +.nav a.home, .nav a.list, .nav a.create { + background-position: 0.7em center; + background-repeat: no-repeat; + text-indent: 25px; +} + +.nav a.home { + background-image: url(../images/skin/house.png); +} + +.nav a.list { + background-image: url(../images/skin/database_table.png); +} + +.nav a.create { + background-image: url(../images/skin/database_add.png); +} + +.nav li.dropdown.show ul.dropdown-menu { + background-color: #424649; +} + +/* CREATE/EDIT FORMS AND SHOW PAGES */ + +fieldset, +.property-list { + margin: 0.6em 1.25em 0 1.25em; + padding: 0.3em 1.8em 1.25em; + position: relative; + zoom: 1; + border: none; +} + +.property-list .fieldcontain { + list-style: none; + overflow: hidden; + zoom: 1; +} + +.fieldcontain { + margin-top: 1em; +} + +.fieldcontain label, +.fieldcontain .property-label { + color: #666666; + text-align: right; + width: 25%; +} + +.fieldcontain .property-label { + float: left; +} + +.fieldcontain .property-value { + display: block; + margin-left: 27%; +} + +label { + cursor: pointer; + display: inline-block; + margin: 0 0.5em 0 0; +} + +input, select, textarea { + background-color: #fcfcfc; + border: 1px solid #cccccc; + font-size: 1em; + padding: 0.2em 0.4em; +} + +select { + padding: 0.2em 0.2em 0.2em 0; +} + +select[multiple] { + vertical-align: top; +} + +textarea { + width: 250px; + height: 150px; + overflow: auto; /* IE always renders vertical scrollbar without this */ + vertical-align: top; +} + +input[type=checkbox], input[type=radio] { + background-color: transparent; + border: 0; + padding: 0; +} + +input:focus, select:focus, textarea:focus { + background-color: #ffffff; + border: 1px solid #eeeeee; + outline: 0; + -moz-box-shadow: 0 0 0.5em #ffffff; + -webkit-box-shadow: 0 0 0.5em #ffffff; + box-shadow: 0 0 0.5em #ffffff; +} + +.required-indicator { + color: #cc0000; + display: inline-block; + font-weight: bold; + margin-left: 0.3em; + position: relative; + top: 0.1em; +} + +ul.one-to-many { + display: inline-block; + list-style-position: inside; + vertical-align: top; +} + +ul.one-to-many li.add { + list-style-type: none; +} + +/* EMBEDDED PROPERTIES */ + +fieldset.embedded { + background-color: transparent; + border: 1px solid #CCCCCC; + margin-left: 0; + margin-right: 0; + padding-left: 0; + padding-right: 0; + -moz-box-shadow: none; + -webkit-box-shadow: none; + box-shadow: none; +} + +fieldset.embedded legend { + margin: 0 1em; +} + +/* MESSAGES AND ERRORS */ + +.errors, +.message { + margin: 1em 0; + padding: 0.5em; +} + +.message { + background: #f3f3ff; + border: 1px solid #b2d1ff; + color: #006dba; + -moz-box-shadow: 0 0 0.25em #b2d1ff; + -webkit-box-shadow: 0 0 0.25em #b2d1ff; + box-shadow: 0 0 0.25em #b2d1ff; +} + +.errors { + background: #fff3f3; + border: 1px solid #ffaaaa; + color: #cc0000; + -moz-box-shadow: 0 0 0.25em #ff8888; + -webkit-box-shadow: 0 0 0.25em #ff8888; + box-shadow: 0 0 0.25em #ff8888; +} + +.errors ul, +.message { + padding: 0; +} + +.errors li { + list-style: none; + background: transparent url(../images/skin/exclamation.png) 0.5em 50% no-repeat; + text-indent: 2.2em; +} + +.message { + background: transparent url(../images/skin/information.png) 0.5em 50% no-repeat; + text-indent: 2.2em; +} + +/* form fields with errors */ + +.error input, .error select, .error textarea { + background: #fff3f3; + border-color: #ffaaaa; + color: #cc0000; +} + +.error input:focus, .error select:focus, .error textarea:focus { + -moz-box-shadow: 0 0 0.5em #ffaaaa; + -webkit-box-shadow: 0 0 0.5em #ffaaaa; + box-shadow: 0 0 0.5em #ffaaaa; +} + +/* same effects for browsers that support HTML5 client-side validation (these have to be specified separately or IE will ignore the entire rule) */ + +input:invalid, select:invalid, textarea:invalid { + background: #fff3f3; + border-color: #ffaaaa; + color: #cc0000; +} + +input:invalid:focus, select:invalid:focus, textarea:invalid:focus { + -moz-box-shadow: 0 0 0.5em #ffaaaa; + -webkit-box-shadow: 0 0 0.5em #ffaaaa; + box-shadow: 0 0 0.5em #ffaaaa; +} + +/* TABLES */ + +table { + border: 1px solid #DFDFDF; + border-collapse: collapse; + width: 100%; + margin-bottom: 1em; +} + +tr { + border: 0; +} + +tr>td:first-child, tr>th:first-child { + padding-left: 1.25em; +} + +tr>td:last-child, tr>th:last-child { + padding-right: 1.25em; +} + +td, th { + line-height: 1.5em; + padding: 0.5em 0.6em; + text-align: left; + vertical-align: top; +} + +th { + background-color: #efefef; + background-image: -moz-linear-gradient(top, #ffffff, #eaeaea); + background-image: -webkit-gradient(linear, left top, left bottom, color-stop(0, #ffffff), color-stop(1, #eaeaea)); + filter: progid:DXImageTransform.Microsoft.gradient(startColorStr = '#ffffff', EndColorStr = '#eaeaea'); + -ms-filter: "progid:DXImageTransform.Microsoft.gradient(startColorStr='#ffffff', EndColorStr='#eaeaea')"; + color: #666666; + font-weight: bold; + line-height: 1.7em; + padding: 0.2em 0.6em; +} + +thead th { + white-space: nowrap; +} + +th a { + display: block; + text-decoration: none; +} + +th a:link, th a:visited { + color: #666666; +} + +th a:hover, th a:focus { + color: #333333; +} + +th.sortable a { + background-position: right; + background-repeat: no-repeat; + padding-right: 1.1em; +} + +th.asc a { + background-image: url(../images/skin/sorted_asc.gif); +} + +th.desc a { + background-image: url(../images/skin/sorted_desc.gif); +} + +.odd { + background: #f7f7f7; +} + +.even { + background: #ffffff; +} + +th:hover, tr:hover { + background: #f5f5f5; +} + +/* PAGINATION */ + +.pagination { + border-top: 0; + margin: 0.8em 1em 0.3em; + padding: 0.3em 0.2em; + text-align: center; + -moz-box-shadow: 0 0 3px 1px #AAAAAA; + -webkit-box-shadow: 0 0 3px 1px #AAAAAA; + box-shadow: 0 0 3px 1px #AAAAAA; + background-color: #EFEFEF; +} + +.pagination a, +.pagination .currentStep { + color: #666666; + display: inline-block; + margin: 0 0.1em; + padding: 0.25em 0.7em; + text-decoration: none; + -moz-border-radius: 0.3em; + -webkit-border-radius: 0.3em; + border-radius: 0.3em; +} + +.pagination a:hover, .pagination a:focus, +.pagination .currentStep { + background-color: #999999; + color: #ffffff; + outline: none; + text-shadow: 1px 1px 1px rgba(0, 0, 0, 0.8); +} + +.no-borderradius .pagination a:hover, .no-borderradius .pagination a:focus, +.no-borderradius .pagination .currentStep { + background-color: transparent; + color: #444444; + text-decoration: underline; +} + +/* ACTION BUTTONS */ + +.buttons { + background-color: #efefef; + overflow: hidden; + padding: 0.3em; + -moz-box-shadow: 0 0 3px 1px #aaaaaa; + -webkit-box-shadow: 0 0 3px 1px #aaaaaa; + box-shadow: 0 0 3px 1px #aaaaaa; + margin: 0.1em 0 0 0; + border: none; +} + +.buttons input, +.buttons a { + background-color: transparent; + border: 0; + color: #666666; + cursor: pointer; + display: inline-block; + margin: 0 0.25em 0; + overflow: visible; + padding: 0.25em 0.7em; + text-decoration: none; + + -moz-border-radius: 0.3em; + -webkit-border-radius: 0.3em; + border-radius: 0.3em; +} + +.buttons input:hover, .buttons input:focus, +.buttons a:hover, .buttons a:focus { + background-color: #999999; + color: #ffffff; + outline: none; + text-shadow: 1px 1px 1px rgba(0, 0, 0, 0.8); + -moz-box-shadow: none; + -webkit-box-shadow: none; + box-shadow: none; +} + +.no-borderradius .buttons input:hover, .no-borderradius .buttons input:focus, +.no-borderradius .buttons a:hover, .no-borderradius .buttons a:focus { + background-color: transparent; + color: #444444; + text-decoration: underline; +} + +.buttons .delete, .buttons .edit, .buttons .save { + background-position: 0.7em center; + background-repeat: no-repeat; + text-indent: 25px; +} + +.buttons .delete { + background-image: url(../images/skin/database_delete.png); +} + +.buttons .edit { + background-image: url(../images/skin/database_edit.png); +} + +.buttons .save { + background-image: url(../images/skin/database_save.png); +} + +a.skip { + position: absolute; + left: -9999px; +} + +.grails-logo-container { + background: #7c7c7c no-repeat 50% 30%; + margin-bottom: 20px; + color: white; + height:300px; + text-align:center; +} + +img.grails-logo { + height:340px; + margin-top:-10px; +} diff --git a/examples/app1/grails-app/assets/stylesheets/mobile.css b/examples/app1/grails-app/assets/stylesheets/mobile.css new file mode 100644 index 0000000..36feca9 --- /dev/null +++ b/examples/app1/grails-app/assets/stylesheets/mobile.css @@ -0,0 +1,101 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +/* Styles for mobile devices */ + +@media screen and (max-width: 480px) { + .nav { + padding: 0.5em; + } + + .nav li { + margin: 0 0.5em 0 0; + padding: 0.25em; + } + + /* Hide individual steps in pagination, just have next & previous */ + .pagination .step, .pagination .currentStep { + display: none; + } + + .pagination .prevLink { + float: left; + } + + .pagination .nextLink { + float: right; + } + + /* pagination needs to wrap around floated buttons */ + .pagination { + overflow: hidden; + } + + /* slightly smaller margin around content body */ + fieldset, + .property-list { + padding: 0.3em 1em 1em; + } + + input, textarea { + width: 100%; + -moz-box-sizing: border-box; + -webkit-box-sizing: border-box; + -ms-box-sizing: border-box; + box-sizing: border-box; + } + + select, input[type=checkbox], input[type=radio], input[type=submit], input[type=button], input[type=reset] { + width: auto; + } + + /* hide all but the first column of list tables */ + .scaffold-list td:not(:first-child), + .scaffold-list th:not(:first-child) { + display: none; + } + + .scaffold-list thead th { + text-align: center; + } + + /* stack form elements */ + .fieldcontain { + margin-top: 0.6em; + } + + .fieldcontain label, + .fieldcontain .property-label, + .fieldcontain .property-value { + display: block; + float: none; + margin: 0 0 0.25em 0; + text-align: left; + width: auto; + } + + .errors ul, + .message p { + margin: 0.5em; + } + + .error ul { + margin-left: 0; + } +} diff --git a/examples/app1/grails-app/conf/application.yml b/examples/app1/grails-app/conf/application.yml new file mode 100644 index 0000000..e1652f8 --- /dev/null +++ b/examples/app1/grails-app/conf/application.yml @@ -0,0 +1,30 @@ +info: + app: + name: '@info.app.name@' + version: '@info.app.version@' + grailsVersion: '@info.app.grailsVersion@' +dataSource: + driverClassName: org.h2.Driver + username: sa + password: '' + pooled: true + jmxExport: true +environments: + development: + dataSource: + dbCreate: create-drop + url: jdbc:h2:mem:devDb;LOCK_TIMEOUT=10000;DB_CLOSE_ON_EXIT=FALSE + test: + dataSource: + dbCreate: update + url: jdbc:h2:mem:testDb;LOCK_TIMEOUT=10000;DB_CLOSE_ON_EXIT=FALSE + # logSql: true + production: + dataSource: + dbCreate: none + url: jdbc:h2:./prodDb;LOCK_TIMEOUT=10000;DB_CLOSE_ON_EXIT=FALSE +hibernate: + cache: + queries: false + use_second_level_cache: false + use_query_cache: false \ No newline at end of file diff --git a/examples/app1/grails-app/conf/logback-spring.xml b/examples/app1/grails-app/conf/logback-spring.xml new file mode 100644 index 0000000..e5cb8c8 --- /dev/null +++ b/examples/app1/grails-app/conf/logback-spring.xml @@ -0,0 +1,33 @@ + + + + + true + + ${CONSOLE_LOG_THRESHOLD} + + + ${CONSOLE_LOG_PATTERN} + ${CONSOLE_LOG_CHARSET} + + + + + + + + + + + + + + + + + diff --git a/examples/app1/grails-app/conf/spring/resources.groovy b/examples/app1/grails-app/conf/spring/resources.groovy new file mode 100644 index 0000000..fa95006 --- /dev/null +++ b/examples/app1/grails-app/conf/spring/resources.groovy @@ -0,0 +1,3 @@ +// Place your Spring DSL code here +beans = { +} diff --git a/examples/app1/grails-app/controllers/app/UrlMappings.groovy b/examples/app1/grails-app/controllers/app/UrlMappings.groovy new file mode 100644 index 0000000..f49b071 --- /dev/null +++ b/examples/app1/grails-app/controllers/app/UrlMappings.groovy @@ -0,0 +1,16 @@ +package app + +class UrlMappings { + static mappings = { + "/$controller/$action?/$id?(.$format)?"{ + constraints { + // apply constraints here + } + } + + "/"(view:"/index") + "500"(view:'/error') + "404"(view:'/notFound') + + } +} diff --git a/src/test/groovy/gorm/logical/delete/test/Person.groovy b/examples/app1/grails-app/domain/app/Person.groovy similarity index 55% rename from src/test/groovy/gorm/logical/delete/test/Person.groovy rename to examples/app1/grails-app/domain/app/Person.groovy index 4748593..3b595d1 100644 --- a/src/test/groovy/gorm/logical/delete/test/Person.groovy +++ b/examples/app1/grails-app/domain/app/Person.groovy @@ -1,20 +1,16 @@ -package gorm.logical.delete.test +package app // tag::person_class[] -import gorm.logical.delete.LogicalDelete +import grails.logical.delete.LogicalDelete -// end::person_class[] -import grails.gorm.annotation.Entity - -@Entity -// tag::person_class[] class Person implements LogicalDelete { + String userName static mapping = { // the deleted property may be configured // like any other persistent property... - deleted column:"delFlag" + deleted(column: 'delFlag') } } // end::person_class[] diff --git a/grails-app/init/gorm/logical/delete/Application.groovy b/examples/app1/grails-app/init/app/Application.groovy similarity index 65% rename from grails-app/init/gorm/logical/delete/Application.groovy rename to examples/app1/grails-app/init/app/Application.groovy index de70a1b..0931f30 100644 --- a/grails-app/init/gorm/logical/delete/Application.groovy +++ b/examples/app1/grails-app/init/app/Application.groovy @@ -1,12 +1,13 @@ -package gorm.logical.delete +package app -import grails.boot.* +import groovy.transform.CompileStatic + +import grails.boot.GrailsApp import grails.boot.config.GrailsAutoConfiguration -import grails.plugins.metadata.* -@PluginSource +@CompileStatic class Application extends GrailsAutoConfiguration { static void main(String[] args) { GrailsApp.run(Application, args) } -} +} \ No newline at end of file diff --git a/examples/app1/grails-app/services/app/PeopleDataService.groovy b/examples/app1/grails-app/services/app/PeopleDataService.groovy new file mode 100644 index 0000000..7f405d8 --- /dev/null +++ b/examples/app1/grails-app/services/app/PeopleDataService.groovy @@ -0,0 +1,18 @@ +package app + +import grails.logical.delete.annotations.WithDeleted + +import grails.gorm.services.Service + +// tag::peopleDataService[] +@Service(Person) +interface PeopleDataService { + + List listPeople() + + @WithDeleted + List listPeopleWithDeleted() + + void delete(Serializable id) +} +// end::peopleDataService[] \ No newline at end of file diff --git a/examples/app1/grails-app/services/app/PeopleService.groovy b/examples/app1/grails-app/services/app/PeopleService.groovy new file mode 100644 index 0000000..9925026 --- /dev/null +++ b/examples/app1/grails-app/services/app/PeopleService.groovy @@ -0,0 +1,39 @@ +package app + +import grails.logical.delete.annotations.WithDeleted + +import grails.gorm.transactions.Transactional + +@Transactional +class PeopleService { + + List all() { + Person.findAll() + } + + @WithDeleted + List allWithDeleted() { + Person.findAll() + } + + Person add(String userName, Long id = null) { + def p = new Person(userName: userName) + if (id) { + p.id = id + } + p.save() + } + + void clearAll(boolean hard = false) { + Person.withDeleted { + Person.list()*.delete(hard: hard) + } + Person.withSession { org.hibernate.Session session -> + session.with { + flush() + clear() + createNativeQuery('ALTER TABLE person ALTER COLUMN id RESTART WITH 1').executeUpdate() + } + } + } +} \ No newline at end of file diff --git a/grails-app/views/error.gsp b/examples/app1/grails-app/views/error.gsp old mode 100755 new mode 100644 similarity index 100% rename from grails-app/views/error.gsp rename to examples/app1/grails-app/views/error.gsp diff --git a/examples/app1/grails-app/views/index.gsp b/examples/app1/grails-app/views/index.gsp new file mode 100644 index 0000000..1d34fe1 --- /dev/null +++ b/examples/app1/grails-app/views/index.gsp @@ -0,0 +1,78 @@ + + + + + Welcome to Grails + + + + + + + + + + +
+
+
+

Welcome to Grails

+ +

+ Congratulations, you have successfully started your first Grails application! At the moment + this is the default page, feel free to modify it to either redirect to a controller or display + whatever content you may choose. Below is a list of controllers that are currently deployed in + this application, click on each to execute its default action: +

+ + +
+
+
+ + + diff --git a/examples/app1/grails-app/views/layouts/main.gsp b/examples/app1/grails-app/views/layouts/main.gsp new file mode 100644 index 0000000..5ce33c2 --- /dev/null +++ b/examples/app1/grails-app/views/layouts/main.gsp @@ -0,0 +1,73 @@ + + + + + + + <g:layoutTitle default="Grails"/> + + +