This is an automated email from the ASF dual-hosted git repository. jdaugherty pushed a commit to branch main in repository https://gitbox.apache.org/repos/asf/grails-intellij-plugin.git
commit f5a0509f69e78e29801d45c432facf347567a853 Author: James Daugherty <[email protected]> AuthorDate: Sat Jul 18 15:23:34 2026 -0400 Add ASF migration and improvement plans --- IMPROVEMENT-PLAN.md | 384 ++++++++++++++++++++++++++++++++++++++++++++++++++++ MIGRATION-PLAN.md | 363 +++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 747 insertions(+) diff --git a/IMPROVEMENT-PLAN.md b/IMPROVEMENT-PLAN.md new file mode 100644 index 0000000..107e4f3 --- /dev/null +++ b/IMPROVEMENT-PLAN.md @@ -0,0 +1,384 @@ +# Improvement Plan — grails-intellij-plugin + +Companion to `MIGRATION-PLAN.md`. That plan makes the repo ASF-compliant; this plan +identifies functional gaps and sequences how to close them. Every phase below was +detailed by examining the **grails-core monorepo** (surveyed 2026-07-07, `main` = +`8.0.0-SNAPSHOT`); concrete class names, coordinates, and DSLs cited come from that +source tree. + +**Two-plugin strategy** (per MIGRATION-PLAN amendment 2026-07-07): a *legacy* plugin +(id `org.intellij.grails`, forked at the compliance tag, Grails 2.x–6.x, maintenance +only) and a *new* plugin (fresh Apache id, Grails 7+ only). Unless marked otherwise, +the phases below target the **new** plugin; the legacy plugin receives only Phase 1 +(platform health) on a recurring basis. + +> Version note: grails-core `main` is 8.0.0-SNAPSHOT (Groovy 5.0.7, Spring Boot 4.1, +> Java 21, Gradle 9.6, Hibernate 7 default). Grails 7.x is the current release line +> and the first under `org.apache.grails` coordinates. Where 7.x and 8.x differ +> (artifact renames, gradle plugin ids), Phase 0 must verify against a real 7.0.x app +> — the facts below are authoritative for 8.x and directionally right for 7.x. + +## What we have today (surveyed 2026-07-07) + +- ~635 main sources (525 Java, 110 Kotlin) + 11 submodules; 184 test classes. +- Strong core: full GSP language (lexer/parser/PSI, formatting, folding, completion, + HTML/Groovy/CSS/JS injection), 20+ Groovy member contributors (GORM criteria, named + queries, constraints), artefact handlers, URL-mapping references, Spring bean + discovery, run configurations, project view pane, GSON views support, templates. +- Dual-era structure model: `Grails2Application`/`OldGrails*` (BuildConfig.groovy, + Gant) alongside `Grails3*` (Gradle-based). +- Era artifacts: `pluginSupport/` for Searchable, Shiro, WebFlow, Resources (dead); + `testdata/` mock Grails **1.x** JARs. +- Ultimate-only (`com.intellij.modules.ultimate` + javaee/persistence/jsp/spring/ + database deps). Targets platform 2025.3, IntelliJ Platform Gradle Plugin 2.11.0. + +--- + +## Phase 0 — Baseline audit (applies to the shared codebase, pre-fork) + +Produce `GAPS.md` (or one GitHub issue per finding, labeled `audit`) before changing +anything. Concrete checklist: + +**0.1 Build & platform debt** +- [ ] `./gradlew check` — record failing/skipped tests per module. +- [ ] `./gradlew verifyPlugin` against 2025.3 + latest EAP — inventory every + deprecated/internal/removed API usage (this is the legacy plugin's recurring + maintenance surface, so severity-rank it). +- [ ] Confirm the `gen/` GSP lexer sources regenerate from their JFlex inputs; document + the regeneration command in `DEVELOPMENT.md`. + +**0.2 Real-app walkthrough** — generate three apps and open each with the plugin: +1. Grails **7.0.x** release (via `https://start.grails.org` — forge production + endpoint) with the `web` application type, +2. Grails **8.0.0-SNAPSHOT** (forge snapshot endpoint + `https://grailsforge-snapshot-cjmq3uyfcq-uc.a.run.app/`), +3. Grails **6.x** (legacy baseline, for the legacy plugin's regression record). + +Walk this checklist per app and record works / broken / missing: +- [ ] Project recognized as Grails; `grails-app/*` project view pane renders + controllers/domain/services/taglib/views/conf/i18n/**init**/**utils**/assets + groups (init/ and utils/ are conventions the plugin may predate). +- [ ] `Application.groovy` in `grails-app/init/` (extends + `grails.boot.config.GrailsAutoConfiguration`, runs via `GrailsApp.run`) is + recognized as the run entry point. +- [ ] Domain class in `grails-app/domain/`: GORM static/instance member completion + (see Phase 2.3 list), constraints block completion, mapping block completion, + dynamic finder resolution, `where {}` closure typing. +- [ ] Data Service interface with `@grails.gorm.services.Service(Book)`: implemented- + method recognition (no "cannot instantiate interface" errors, method-convention + completion). +- [ ] Controller: action navigation, `respond`/`render`, interceptor `match()`/ + `matchAll()` DSL, `UrlMappings.mappings` closure completion. +- [ ] GSP: `g:` tag completion + navigation to taglib closures; `f:` namespace from + grails-fields; layout tags; `asset:` taglib (asset-pipeline). +- [ ] `.gson` views: `json {}` / `model {}` DSL, `g`/`tmpl`/`hal`/`jsonapi` helpers; + `.gml` markup views (expected: **no support at all** — confirm). +- [ ] `application.yml`: completion for `grails.*` keys; `runtime.groovy`/ + `application.groovy` recognized; `spring/resources.groovy` bean DSL. +- [ ] Run/debug: `bootRun` launch + debug attach, unit test run (Spock 2.4 traits), + `integrationTest` task (from `TestPhasesGradlePlugin` source set + `src/integration-test/groovy`), `@Integration` test debug. +- [ ] New-artefact actions produce Grails-7-correct file stubs (compare against + `grails-profiles/web/templates/artifacts/` in grails-core). +- [ ] Version/framework detection: does the plugin light up at all when only + `org.apache.grails` coordinates are present? (Expected failure: detection almost + certainly keys on `org.grails` group ids — confirm exact detection code paths in + `Grails3ApplicationProvider`, `GrailsUtils`, SDK/library detection.) + +**0.3 Scoping counts (feed Phases 3–4)** +- [ ] Count references from core packages into Ultimate-only APIs (spring, javaee, + persistence, database plugins) — data for the Community-edition split. +- [ ] Map which classes are reachable only from `OldGrails*`/Gant/BuildConfig paths — + the safe-deletion list for the new plugin's pruning step. +- [ ] List every stub index / file-based index and its version key (needed for the + package-rename impact assessment in the new plugin). + +--- + +## Phase 1 — Platform health (both plugins; recurring for legacy) + +- [ ] Fix all `verifyPlugin` **error**-level findings (removed/internal APIs). +- [ ] Provide new-UI (20×20) icon variants for the gutter/toolwindow/artefact icons; + audit `GrailsIconProvider` and `GroovyMvcIcons` (generated) coverage. +- [ ] Verify dumb-mode safety of the heavier contributors (member contributors and + reference providers should be index-tolerant). +- [ ] Encode the supported-platform window in `pluginVerification.ides` (proposal: + latest two IDE majors for the new plugin; "current major only, best effort" for + legacy). +- [ ] Legacy-specific: define the recurring platform-bump procedure (bump + `sinceBuild`, run verifier, fix errors, release `<branch>.x.y`) and put it in + the legacy repo's `DEVELOPMENT.md`. + +--- + +## Phase 2 — Grails 7+/8 support (new plugin; the headline work) + +### 2.1 Framework detection & versioning + +Detection must work from Gradle dependency data (the plugin already has a +`GrailsProjectResolverExtension`); the authoritative signals from grails-core: + +- [ ] **Gradle plugin ids**: `org.apache.grails.gradle.grails-app`, `-web`, + `-plugin`, `-gsp`, `-gson`, `-markup`, `-profile` (Apache era). Legacy signal + for ≤6: `org.grails.grails-*` ids. +- [ ] **BOM**: `platform("org.apache.grails:grails-bom:<v>")` (variants: + `grails-bom-hibernate5/-hibernate7/-micronaut`). BOM version = Grails version. +- [ ] **Coordinates**: any `org.apache.grails[.*]:grails-*` dependency ⇒ Apache era. + Sub-groups to recognize: `org.apache.grails.data` (GORM), `.views`, `.web`, + `.testing`, `.i18n`, `.gradle`, `.profiles`. `RENAME.md` in grails-core is the + full old→new artifact map — encode it as a table in one detection class + (e.g. `grails-plugin-controllers` → `grails-controllers`, + `grails-datastore-gorm` → `grails-datamapping-core`). +- [ ] **Corroborating signals** (don't hard-require): Groovy group + `org.apache.groovy` (4/5) vs `org.codehaus.groovy` (≤3); Spring Boot 3 (Grails + 6/7) vs 4.1+ (Grails 8); Java baseline 21 (8.x). +- [ ] Replace/extend `org.jetbrains.plugins.grails.util.Version` handling so 7.x/8.x + version strings parse and feature-gate correctly (e.g. `.gml` support only when + `grails-views-markup` present). + +### 2.2 Project structure model + +- [ ] Single `GrailsApplication` implementation family for Gradle-era apps (7+); + recognize `grails-app/{controllers,domain,services,taglib,views,conf,i18n, + init,utils,assets}` plus `src/main/groovy`, `src/test/groovy`, + `src/integration-test/groovy` (registered by `TestPhasesGradlePlugin`) and + `src/functional-test/groovy` (functional/Geb phase) source sets. +- [ ] `grails-app/conf`: `application.yml` (primary), `application.groovy`, + `runtime.groovy`, `logback-spring.xml`, `spring/resources.groovy` (bean DSL — + keep existing support, verify against Boot 3/4 world). +- [ ] Plugin projects: `*GrailsPlugin` class in `grails-app/init` (or `src/main/ + groovy`), lifecycle hooks `doWithSpring`/`doWithDynamicMethods`/ + `doWithApplicationContext`/`onChange`/`onConfigChange`/`onShutdown` — verify + completion inside these closures. + +### 2.3 Artefact handlers & GORM code insight + +Artefact type list to support (from grails-core artefact handlers): +Controller, Domain, Service, TagLib, UrlMappings (fixed name, lives in +`controllers/`), BootStrap (fixed name, `init/`), Codec, Interceptor, Application +(`init/`), plus **Data Service** (new): + +- [ ] **Data Services**: `@grails.gorm.services.Service(Domain)` on interfaces / + abstract classes in `grails-app/services/`. IDE must: suppress + "unimplemented method" errors (AST-generated impl), complete/resolve + convention methods (`get`, `list`, `count`, `save`, `delete`, `find*By*`), + and gutter-mark the generated implementation. +- [ ] **GormEntity member injection** — align the existing NonCodeMemberProcessor + lists with GORM 9's `org.grails.datastore.gorm.GormEntity` trait. Instance: + `save/insert/merge/delete/refresh/attach/discard/lock/ident/isDirty/ + getDirtyPropertyNames/addTo*/removeFrom*/instanceOf`. Static: `get/read/load/ + proxy/getAll/list/first/last/count/exists/saveAll/deleteAll/where/whereLazy/ + whereAny/createCriteria/withCriteria/findWhere/findAllWhere/findOrCreateWhere/ + findOrSaveWhere/executeQuery/executeUpdate/withTransaction/withNewTransaction/ + withSession/withNewSession/withStatelessSession` + dynamic finders. +- [ ] **Dynamic finder grammar** (`org.grails.datastore.gorm.finders.*`): prefixes + `findBy/findAllBy/countBy/listOrderBy/findOrCreateBy/findOrSaveBy`; comparators + `Equal/NotEqual/InList/NotInList/Between/InRange/Like/Ilike/Rlike/GreaterThan + [Equals]/LessThan[Equals]/IsNull/IsNotNull/IsEmpty/IsNotEmpty`; `And`/`Or` + combinators; trailing Map args (`max/offset/sort/order/ignoreCase/fetch/cache/ + lock`). +- [ ] **where/DetachedCriteria**: type the `where {}` closure delegate as + `grails.gorm.DetachedCriteria<T>` with property-operator DSL (`==`, `!=`, `<`, + `in`, `like`, …) and `and/or/not/order/join/projections` blocks. +- [ ] **Constraints block**: built-in list from + `org.grails.datastore.gorm.validation.constraints.*` — nullable, blank, size, + minSize, maxSize, min, max, range, inList, matches, email, url, creditCard, + validator, unique, notEqual, scale. (Diff against the plugin's current + hardcoded list — it dates from Grails 2.) +- [ ] **mapping block**: Hibernate mapping DSL from + `org.grails.orm.hibernate.cfg.Mapping` (table, id/generator, version, cache, + sort, batchSize, dynamicInsert/Update, per-property column/sqlType/lazy/ + formula/index). +- [ ] **jakarta, not javax**: domain/JPA integration (the `hibernate/` submodule and + persistence facet code) must use `jakarta.persistence.*`. + +### 2.4 Web layer: GSP, taglibs, views, URL mappings, interceptors + +- [ ] **Taglib registry refresh** — the `g:` namespace tag set should be sourced from + the app classpath (taglib classes with `@TagLib`/namespace field) rather than a + static list; current core taglibs in grails-core: + `ApplicationTagLib` (link, createLink, resource, img, set, cookie, header, + external, join, meta, applyCodec, flashMessages), `FormTagLib` (form, + textField, passwordField, hiddenField, checkBox, textArea, datePicker, + submitButton, uploadForm, …), `ValidationTagLib` (message, hasErrors, + eachError, fieldError, fieldValue), `FormatTagLib` (formatDate, formatNumber, + formatBoolean, encodeAs), `UrlMappingTagLib` (include, paginate), + `JavascriptTagLib`, `RenderTagLib` (render, renderException), layout tags + (layoutBody/layoutHead/layoutTitle/pageProperty/ifPageProperty/applyLayout — + Sitemesh2 still, packaged as `org.apache.grails:grails-layout`). +- [ ] **`f:` namespace** (grails-fields `grails.plugin.formfields.FormFieldsTagLib`): + `with`, `all`, `field`, `table`, `display` — grails-fields is now in the + monorepo, so treat it as first-party. +- [ ] **`.gson` JSON views**: verify against current `grails-views-gson` — + script base `grails.plugin.json.view.JsonViewWritableScript`, `json {}` / + `json(list)` entry points, implicit `model {}`, helpers `g` + (`GrailsJsonViewHelper`), `hal`, `jsonapi`, `tmpl` (`TemplateRenderer`); + `_partial` template resolution and `/object/_object.gson` convention. +- [ ] **`.gml` markup views — NEW file type**: register extension, inject Groovy + `MarkupTemplateEngine` DSL, `g` helper (`GrailsViewHelper`). Gate on + `grails-views-markup` on classpath. +- [ ] **UrlMappings DSL**: completion inside `static mappings = {}` — pattern-string + entries, `constraints {}`, `group`, `resources`/nested resources, response-code + mappings (`"404"(...)`) , attrs `controller/action/view/method/namespace`. + Cross-reference: mapping ↔ controller action ↔ view. +- [ ] **Interceptors**: `grails.artefact.Interceptor` trait members (`match(Map)`, + `matchAll()`, matcher chain `.excludes/.except`, lifecycle + `before/after/afterView`) — completion + "matched controllers" navigation. +- [ ] **Codecs**: keep `encodeAs*` dynamic-method support; built-ins HTML, HTML4, + JavaScript, HTMLJS, URL, Raw (`org.grails.plugins.codecs`). + +### 2.5 Config files + +- [ ] `application.yml`/`application.groovy` key completion sourced from + `spring-configuration-metadata.json` on the app classpath (Boot metadata) plus + the Grails namespace defaults seen in the profile skeletons + (`grails.codegen.defaultPackage`, `grails.profile`, `grails.views.gsp.*`, + `info.app.*` with `@…@` Gradle interpolation). +- [ ] Retire `BuildConfig.groovy`/`Config.groovy` analysis from the new plugin + (moves to legacy only). + +### 2.6 Run, test, and command execution + +- [ ] Run config delegates to Gradle `bootRun` (Boot 3/4 `BootRun` task; dev-mode PID + file `run-app.pid`); debug via standard Gradle/JVM attach. +- [ ] Test integration: unit-test traits from `grails-testing-support-*` + (`grails.testing.services.ServiceUnitTest`, `grails.testing.web.controllers. + ControllerUnitTest`, `grails.testing.gorm.DomainUnitTest`, `grails.testing.web. + taglib.TagLibUnitTest`, `grails.testing.web.interceptor.InterceptorUnitTest`, + `grails.testing.web.UrlMappingsUnitTest`), `@grails.testing.mixin.integration. + Integration` for integration tests, Geb (`org.apache.groovy.geb:geb-spock`) + for functional. Spock 2.4. Run line markers on all of these; route + `integrationTest` through the Gradle task, not a JUnit config. +- [ ] Command layer: replace `Grails3CommandProvider` sources with (a) profile-yml + commands (`create-controller`, `create-service`, `create-taglib`, + `create-interceptor`, `create-domain-class`, `create-integration-test` from + `grails-profiles/*/commands/`) and (b) forge-cli commands + (`org.grails.forge.cli.command.Create*Command`, incl. `create-job`, + `create-scaffold-controller` era equivalents). Scaffolding: `@grails.plugin. + scaffolding.annotation.Scaffold` recognition + `generate-*` commands. +- [ ] New-project wizard: call the forge HTTP API (`https://start.grails.org`; API + `/application-types` for types/features/gorm-impls/JDKs, POST `/create` for + generation) with an offline fallback via `org.grails.forge:grails-forge-cli`. + This replaces the existing JetBrains-era forge module builder endpoint. + +### 2.7 Test fixtures + +- [ ] Replace mock Grails 1.x JARs in `testdata/` with fixtures from a pinned + Grails 7/8 release (align with MIGRATION-PLAN Phase 3's source-release + decision; prefer test-time fetch of real `org.apache.grails` artifacts). +- [ ] Add whole-project test fixtures for the 7.x layout (web app + plugin project + + data-service usage) so modern support has regression coverage — today's suite + guards the legacy era. + +--- + +## Phase 3 — Creating the new plugin (fork + prune + rename) + +Per the two-plugin strategy, this is no longer a "should we remove?" debate — it's +the construction step of the new plugin, immediately after the compliance fork: + +- [ ] New plugin id (proposal: `org.apache.grails.intellij`) + fresh Marketplace + listing under the Apache vendor; declare mutual exclusivity with + `org.intellij.grails` (both directions). +- [ ] Package rename `org.jetbrains.plugins.grails.*` / `org.jetbrains.plugins. + groovy.mvc.*` → `org.apache.grails.intellij.*`: mechanical, but must update + every FQN in `plugin.xml`/module XMLs, bump all stub-index version keys + (inventory from Phase 0.3), and accept fresh persisted settings (new plugin = + no migration burden). +- [ ] Delete, using the Phase 0.3 reachability map: + - `Grails2Application`, `OldGrails*` (application, command provider, node + provider), module-based structure sync paths in + `MvcModuleStructureSynchronizer` (keep what Gradle-era import still uses), + - BuildConfig.groovy/Config.groovy analysis, Gant script support, + `grails-compiler-patch` + `jps-plugin` if they only serve the pre-Gradle + compile pipeline (verify in Phase 0), + - `pluginSupport/`: searchable, shiro, webflow, resources (keep assetPipeline, + spock, buildTestData only if still ecosystem-relevant — asset-pipeline yes, + spock yes), + - Grails 1.x/2.x testdata and the tests that exercise deleted paths. +- [ ] Re-run the full test suite + `verifyPlugin`; the survivor test set defines the + new plugin's guarded behavior. +- [ ] Legacy repo: no code changes beyond the Phase 1 maintenance policy; add a + README banner and Marketplace description pointing Grails 7+ users to the new + plugin. + +--- + +## Phase 4 — Audience: Community-edition support (new plugin) + +`com.intellij.modules.ultimate` + javaee/jsp/spring/database deps make the plugin +invisible to IntelliJ Community users. Using the Phase 0.3 reference counts: + +- [ ] Restructure `plugin.xml` so the core loads on Community: GSP language + editing, + artefact recognition/navigation, GORM completion, run configs, project view, + forge wizard. +- [ ] Move to `<depends optional="true" config-file=…>` modules: Spring bean + integration (`com.intellij.spring`), JPA/persistence + the `hibernate/` + submodule (`com.intellij.persistence`, `com.intellij.javaee.jpa`), database + integration (`com.intellij.database`), JSP-adjacent GSP features + (`com.intellij.jsp`), JS/CSS injection (already optional). +- [ ] The existing submodule layout (`hibernate/`, `langInjection/`, `i18n/`, …) is + most of the needed seam — the work is breaking compile-time references from + core packages into Ultimate-only APIs (count known from Phase 0.3). +- [ ] Decide the Marketplace story: one artifact with optional deps (recommended) + vs. core+addon pair. Default to one artifact unless the audit shows an + irreducible Ultimate core. + +--- + +## Phase 5 — Feature roadmap (new plugin, post-stabilization) + +File as issues, prioritize by community feedback. Grounded candidates: + +- [ ] Controller action ↔ GSP view ↔ URL-mapping line markers (the mapping DSL data + from 2.4 makes the triad resolvable). +- [ ] Data Service gutter: `@Service(Book)` interface method → generated implementer + semantics (`org.grails.datastore.gorm.services.ServiceImplementer` variants). +- [ ] Inspections: unknown `application.yml` key (vs. Boot metadata), constraint on + nonexistent property, dynamic finder referencing nonexistent property, taglib + call with unknown attribute, `@Integration` test in unit source set. +- [ ] Quick-doc for GORM injected members and `g:`/`f:` tags (tag descriptions can be + generated from the taglib source Javadoc in grails-core). +- [ ] Live/file template refresh from `grails-profiles/*/templates/artifacts/` + (authoritative artifact stubs), including Data Service and Interceptor + templates; `.gml` markup-view support (2.4). +- [ ] Refactoring: artefact rename cascades (controller rename → views directory + + URL mappings + tests). +- [ ] Micronaut-BOM awareness (`grails-bom-micronaut`, `micronaut-context` config + keys) — low priority until user demand shows. + +--- + +## Phase 6 — Community & sustainability (both repos) + +- [ ] Issue templates/labels mirroring grails-core `.github` conventions; triage the + inherited Marketplace/YouTrack feedback into GitHub issues split across the two + repos (legacy bugs vs. new-plugin features). +- [ ] `DEVELOPMENT.md` per repo: `runIde` sandbox, test layout, GSP lexer + regeneration (`gen/` provenance), platform-bump procedure (legacy), release + procedure pointer to MIGRATION-PLAN Phase 6. +- [ ] Marketplace listings: legacy = renamed "Grails (legacy, Grails 2–6)" with + pointer; new = Apache branding, fresh screenshots against a Grails 7 app. +- [ ] ≥2 committers named as maintainers per plugin line; legacy sunset criteria + reviewed yearly (cap `untilBuild` + archive when Grails 6 usage justifies). + +--- + +## Sequencing & dependencies + +``` +MIGRATION-PLAN Phases 1–4 (compliance, this repo) + │ + ├── Phase 0 audit (read-only, runs in parallel) ──► GAPS.md + │ +first compliant release (feature-frozen re-release of the JetBrains plugin) + │ + ├── fork ──► legacy repo (keeps org.intellij.grails) ──► Phase 1 recurring + │ + └── this repo = new plugin + Phase 3 (prune + rename + new id) + Phase 1 (platform health) + Phase 2 (Grails 7+/8 support) ◄── grails-core facts above + Phase 4 (Community edition) + Phase 5 (features) / Phase 6 (community) +``` diff --git a/MIGRATION-PLAN.md b/MIGRATION-PLAN.md new file mode 100644 index 0000000..797eab2 --- /dev/null +++ b/MIGRATION-PLAN.md @@ -0,0 +1,363 @@ +# ASF Migration Plan — grails-intellij-plugin + +Bring `apache/grails-intellij-plugin` (imported from `JetBrains/intellij-obsolete-plugins`) +into compliance with ASF policy and the grails-core conventions, and stand up GitHub +Actions for building and publishing. + +Unlike prior plugin migrations, this repo **stays standalone** — it cannot merge into the +grails-core monorepo because it builds against the IntelliJ Platform Gradle Plugin +(`org.jetbrains.intellij.platform` 2.11.0), not the Grails BOM/shared-gradle world. We +therefore *port* the grails-core compliance/CI conventions here rather than rewiring onto +shared config. The `mono-repo-integration` skill's principles still apply where relevant +(license headers, RAT excludes for templates/test data, CI gating publish on tests, no +blind deletion of imported infra). + +## Current state (surveyed 2026-07-06) + +- **Build**: Gradle Kotlin DSL, root plugin + 11 subprojects (`copyright`, `coverage`, + `gradle-tooling`, `grails-compiler-patch`, `grails-rt`, `hibernate`, `i18n`, + `jps-plugin`, `langInjection`, `maven`, `testFramework`). IntelliJ Platform Gradle + Plugin 2.11.0, Kotlin 2.3.0, platform 2025.3.1 (`sinceBuild=253`), Java 21 (8/11 for the + runtime/JPS legacy modules). `pluginVersion=253.0.0`. +- **Identity**: plugin id `org.intellij.grails`, vendor **JetBrains**, packages + `org.jetbrains.plugins.grails`. +- **Licensing**: `LICENSE.txt` = Apache-2.0 full text. **No NOTICE.** UPDATED + (2026-07-18): the ~909 source files that carried the abbreviated JetBrains one-line + header (`Copyright 2000-20xx JetBrains s.r.o. and contributors. Use of this source + code is governed by the Apache 2.0 license.`) now carry the **full standard Apache 2.0 + license header** (the license Appendix boilerplate) with the JetBrains copyright line + preserved — *not* the ASF "Licensed to the Apache Software Foundation" header. ~44 + files still have no header (testdata fixtures, `.ft`/`.gsp` templates, properties, + XML). `gen/` is generated code; `testdata/` contains mock Grails 1.x JARs (test + fixtures, not shipped). +- **CI/publishing**: none. No `.github/`, no `.asf.yaml`, no publish or signing config. + +## Open decisions (resolve before/while executing) + +1. **Header strategy for JetBrains-copyright files.** RESOLVED (2026-07-18): **no + software grant / IP clearance is recorded yet**, so the licensing terms in the + per-file headers must not be changed — the files stay attributed to JetBrains and + must NOT receive the ASF "Licensed to the Apache Software Foundation" header until a + grant is provided. What we did instead: expanded every abbreviated JetBrains header + to the full standard Apache 2.0 license header (Appendix boilerplate) while keeping + the `Copyright 2000-20xx JetBrains s.r.o. and contributors.` line. This satisfies + RAT's stock Apache-2.0 detection with no custom matcher. The wholesale ASF-header + replacement (former "option A") moves to Phase 9 (post-grant follow-up) and is + blocked on the software grant being recorded with the ASF Secretary. +2. **Plugin id & Marketplace listing.** RESOLVED (2026-07-06): JetBrains has already + handed the Marketplace listing over. The plugin id `org.intellij.grails` is permanent — + Marketplace ids cannot be changed on an existing listing, and the IDE matches updates + by id, so a new id would orphan the entire install base and break `<depends>` in + third-party plugins. Keep the id forever; update the listing's name/vendor to Apache. + AMENDED (2026-07-07): under the two-plugin strategy below, the id and existing listing + stay with the **legacy** plugin line; the new Grails 7+ plugin gets a fresh Apache id. + The Java packages (`org.jetbrains.plugins.grails.*`) CAN be renamed (nothing external + keys on them), but it means renaming every class reference in plugin.xml/module XMLs, + resets FQN-keyed persisted state for users, and forces stub-index version bumps — and + ASF policy does not require it (cf. Apache Groovy keeping `org.codehaus.groovy`). If + wanted, do it as a dedicated follow-up effort after this migration, never mixed into + the compliance commits. +3. **Develocity / TestLens**: grails-core CI wires `DEVELOCITY_ACCESS_KEY` and TestLens. + Decide whether this repo joins the same Develocity instance; if not, drop those steps. +4. **Snapshot distribution**: attach the plugin ZIP as a workflow artifact on every main + build (simplest, recommended), and/or publish to a Marketplace `eap`/`snapshot` + channel. +5. **Versioning**: keep the `<platform-branch>.x.y` scheme (`253.0.0`) or align with a + Grails-style scheme. Marketplace convention favors the platform-branch scheme — keep it. + +--- + +## Two-plugin strategy (added 2026-07-07) + +Decision: maintain **two plugins** — a *legacy* plugin forked from this codebase that +keeps JetBrains-platform compatibility for older Grails versions (2.x–6.x), and a *new* +plugin that supports only Grails 7+ (Apache-era coordinates, pruned codebase). This is +feasible; the constraints and the plan: + +- **Fork point**: fork *after* Phases 1–4 land in this repo (metadata, headers, RAT, + identity), so both repos inherit the compliance work and neither redoes header + expansion. Note: the post-grant ASF header replacement (Phase 9) will have to run in + **both** repos if the fork happens before the grant lands — keep the replacement + script shared. Feature divergence starts only after the first compliant release. +- **Repos**: this repo (`apache/grails-intellij-plugin`) becomes the home of the **new** + plugin; request a second repo from INFRA (suggested: `grails-intellij-plugin-legacy`) + created as a fork at the compliance tag. Both carry the same `.asf.yaml`/CI shape; + the legacy repo's workflows are identical minus the feature-oriented jobs. +- **Marketplace identity**: + - *Legacy* keeps plugin id `org.intellij.grails` and the transferred listing — the + existing install base auto-updates into it and nobody's Grails 2–6 support silently + disappears. Listing description gains a prominent pointer to the new plugin. + - *New* plugin registers a fresh id (suggested: `org.apache.grails.intellij`) and a + new listing under the Apache vendor. Because it has no persisted-state or listing + history, it is free to rename packages `org.jetbrains.plugins.grails` → + `org.apache.grails.intellij.*` from day one — this retires the package-rename + question deferred in decision 2 (it applies to the new plugin only; legacy never + renames). +- **Mutual exclusivity**: both plugins register the GSP language, file type, run + configuration type, and stub indices — the platform cannot load both. Declare the + conflict (plugin.xml `<incompatible-with>` on each other's id; verify the mechanism + during implementation — if id-level incompatibility isn't honored on the target + platform, add a startup check that notifies and disables) and document "install one, + not both" in both listings. If dual-install demand materializes, the escape hatch is + extracting a shared GSP-core plugin both depend on — out of scope for now. +- **Support policy** (propose to PMC alongside the fork): + - Legacy: **maintenance-only**. Platform-compat fixes (`sinceBuild` bumps, Plugin + Verifier errors), critical bugs. No new features. `verifyPlugin` CI against each new + IDE major is the tripwire. Versioning continues the `<platform-branch>.x.y` scheme. + Sunset criteria (e.g. when Grails 6 usage drops, cap `untilBuild` and archive) to be + revisited yearly. + - New: all feature work (see `IMPROVEMENT-PLAN.md`; its Phase 3 "legacy pruning" + becomes the *creation step* of the new plugin — prune Grails 1/2 support, dead + plugin integrations, and `OldGrails*` structure model immediately after the fork). +- **Cost acknowledged**: double release engineering (two votes, two Marketplace + publishes) and platform bumps twice a year on the legacy line. Mitigated by the + maintenance-only policy and shared workflow templates. + +## Phase 1 — ASF repo metadata + +One commit: "ASF Compliance - repository metadata". + +- **`NOTICE`** (new, required by policy). Model on grails-core's: + + ``` + Apache Grails IntelliJ Plugin + Copyright 2025-2026 The Apache Software Foundation + + This product includes software developed at + The Apache Software Foundation (http://www.apache.org/). + + This product includes software originally developed by JetBrains s.r.o. + and contributors (https://www.jetbrains.com/). + Copyright 2000-2026 JetBrains s.r.o. and contributors. + Licensed under the Apache License, Version 2.0. + ``` + + Do **not** say "donated to the ASF" until the software grant is recorded; add that + wording (confirmed against the grant text) in Phase 9. +- **`HEADER`** (new): copy grails-core's root `HEADER` file verbatim — the canonical ASF + header text ("Licensed to the Apache Software Foundation (ASF) under one or more + contributor license agreements…"). Pre-grant it applies to **new files only** + (decision 1); post-grant it becomes the single source of truth for the wholesale + replacement in Phase 9. +- **`LICENSE`**: rename `LICENSE.txt` → `LICENSE` (grails-core convention). Create a + `licenses/` directory only if/when non-Apache third-party assets are identified (none + found so far — icons are JetBrains-authored and covered by the grant; testdata JARs are + Apache-2.0 Grails/Spring artifacts and are excluded from the source distro anyway — + see Phase 3 decision on testdata JARs). +- **`.asf.yaml`**: adapt grails-core's — `description`, `homepage: + https://grails.apache.org/`, notifications to `[email protected]` / + `[email protected]`, default-branch protection ruleset + (restrict deletion/force-push), and a `release` deployment environment with the same + required reviewers (jdaugherty, matrei, jamesfredley, sbglasius) to gate the release + workflow. +- **`README.md`** (new): what the plugin is, supported IntelliJ versions, build + instructions, ASF links (no release history section, per grails-core docs convention). +- Optional: `CONTRIBUTING.md`, `CODE_OF_CONDUCT.md` pointers to the main project. + +## Phase 2 — License headers (pre-grant form) + +One commit: "ASF Compliance - license headers". **Constraint (decision 1): until a +software grant is recorded, headers keep JetBrains attribution and standard Apache 2.0 +terms — no ASF licensing language on JetBrains-authored files.** + +- DONE (2026-07-18, uncommitted): mechanically expanded every abbreviated JetBrains + header (the `//`, `/* ... */`, `<!-- -->`, and `#` one-line forms, any year range, + ~909 files) to the full standard Apache 2.0 header (Appendix boilerplate: + "Licensed under the Apache License, Version 2.0 …") with the original + `Copyright 2000-20xx JetBrains s.r.o. and contributors.` line and year range + preserved. Files that already carried the full old-style (2007/2008) JetBrains Apache + header were left untouched; a duplicated embedded header in `GspUtil.java` was + de-duplicated. The expansion script lives in the session scratchpad — commit a copy to + `etc/bin/expand-jetbrains-headers.py` for auditability. +- DONE (2026-07-18, uncommitted): headered the previously-unheadered JetBrains-authored + files that RAT flags — grails-rt runtime sources/scripts, `GrailsStandardDSL.gdsl`, + the non-generated `META-INF/*.xml` descriptors, `liveTemplates/gsp.xml`, `grails.tld`, + bundle/log4j/gradle `.properties`, and the JPS services file — all with the same + JetBrains-attributed Apache 2.0 header. Files that are RAT-excluded (testdata + fixtures, templates, forms, description HTML, `MANIFEST.MF`) were left alone. + Genuinely new ASF-authored files get the canonical ASF header. +- **New files** authored after the import (by ICLA-covered contributors) use the + canonical ASF header from the root `HEADER` file — no grant needed for new + contributions. +- Do **not** header: `gen/**` (generated), `testdata/**` fixtures whose content *is* the + test input (adding headers can break parser/position-sensitive tests — same reason + grails-core excludes test resources), file templates under `resources/fileTemplates/**` + (they materialize inside end-user projects — grails-core's "templates people are + expected to use" rule), `.form` GUI-builder files (machine-edited XML), inspection/ + intention description HTML (grails-core excludes `**/*.html`). +- Verify with a grep audit: every non-excluded source file contains either the full + Apache 2.0 header (JetBrains-attributed) or the ASF header (new files). + +## Phase 3 — RAT verification in the build + +One commit: "ASF Compliance - RAT license audit". + +Because Phase 2 expanded the headers to the standard Apache 2.0 boilerplate, RAT's +stock license matcher recognizes them — **no custom JetBrains matcher is needed**. + +DONE (2026-07-18, uncommitted): `org.nosphere.apache.rat` 0.8.1 added to the root +`plugins {}` block with a kts `tasks.rat { excludes... }` config (grails-core's shape, +translated; every exclude justified in a comment; `outputs.upToDateWhen { false }`). +`./gradlew rat` passes: 927 files audited, 0 unknown licenses. Notable repo-specific +exclude: `.intellijPlatform/**` (IntelliJ Platform Gradle Plugin's local cache of +generated Ivy descriptors). Remaining details below for reference. + +- Add the `org.nosphere.apache.rat` plugin (grails-core pins **0.8.1** via + `apacheRatVersion` on the buildSrc classpath; this repo has no buildSrc, so declare + `id("org.nosphere.apache.rat") version "0.8.1"` in the root `plugins {}` block) and port + grails-core's `gradle/rat-root-config.gradle` shape (this repo is Kotlin DSL; either + keep a Groovy `gradle/rat-root-config.gradle` applied via `apply(from = ...)` — closest + to grails-core — or translate to kts). +- Excludes tailored to this repo (mirroring grails-core's categories): + - generated: `gen/**` + - test fixtures: `testdata/**` (includes the mock Grails JARs; binary anyway) + - templates shipped into user projects: `resources/fileTemplates/**`, + `resources/liveTemplates/**` (verify), `**/*.ft`, template `.gsp` + - images: `**/*.png`, `**/*.svg`, `**/*.ico` + - html/json descriptions: `**/*.html`, `**/spring-configuration-metadata.json`-style + JSON (JSON can't carry headers) + - infra: `.asf.yaml`, `.github/**`, `**/.gitignore`, `gradlew*`, `**/wrapper/**`, + `.idea/**`, `**/*.iml`, `out/**`, `build/**` + per-subproject build dirs (use the + same `rootProject.subprojects.collect{...}` trick), `**/*.form` if not headered + - `MIGRATION-PLAN.md` (this file) until deleted +- Set `outputs.upToDateWhen { false }` (never cache audits), like grails-core. +- Iterate `./gradlew rat` until clean; every exclude must carry a justification comment + (grails-core style) — no blanket excludes to make it pass. +- Decision to record: whether the `testdata/**/*.jar` binaries may remain in the + **source release** at all. ASF source releases must not contain compiled code without + justification; likely fine as clearly-labeled test fixtures, but flag it for the PMC + and document in the RAT config comment. Alternative: fetch them at test time. + +## Phase 4 — Identity & conventions cleanup + +One commit: "ASF Compliance - plugin identity". + +- `plugin.xml`: `vendor` → Apache Software Foundation (Grails), vendor URL + `https://grails.apache.org/`, plugin description/links updated; keep plugin id per + decision 2. +- Gradle `group` coordinates: `org.intellij.grails` → `org.apache.grails.intellij` (or + similar) for the module groups — these are internal (nothing publishes to Maven), so + this is low-risk; record the rename in the repo README. Package renames + (`org.jetbrains.plugins.grails` → `org.apache...`) are **out of scope** for this + migration (huge churn, breaks settings/serialized state and plugin.xml extension + points); revisit later if the PMC wants it. +- Apply the grails-core best-practice conventions that translate: 4-space indent, no + wildcard imports, Apache header on every **new** file. CodeNarc does not apply (Java/ + Kotlin codebase); consider ktlint/detekt later, not in this migration. + +## Phase 5 — GitHub Actions: build & verification CI + +One commit: "ASF Compliance - CI workflows". Follow grails-core's patterns: all actions +**pinned to full commit SHAs**, `concurrency` group per workflow+ref, publish gated on +test jobs via `needs:` **and** `if:` result guards. + +- **`.github/workflows/gradle.yml`** — on push/PR to `main` (+ `workflow_dispatch`): + - `build`: checkout (`actions/checkout` @ pinned v6.0.2), `actions/setup-java` + (liberica, 21), `gradle/actions/setup-gradle` @ pinned v6.1.0 with + `cache-provider: basic` (the MIT-licensed provider — grails-core deliberately avoids + the proprietary default) and Develocity key per decision 3; run + `./gradlew check buildPlugin`. + - `verify`: `./gradlew verifyPlugin` (IntelliJ Plugin Verifier against the + `sinceBuild`/target IDE matrix — configure `pluginVerification.ides` in the build to + cover 2025.3 and latest EAP; this is the IntelliJ-world analog of grails-core's + functional matrix and the skill's "reproduce every test axis" rule). + - `snapshot` (main-branch pushes only): upload the `buildPlugin` ZIP as a workflow + artifact; optionally `publishPlugin` to a Marketplace snapshot channel (decision 4). + `needs: [build, verify]` **and** `if:` guards on both results — publishing must be + blocked when tests fail. +- **`.github/workflows/rat.yml`** — copy grails-core's nearly verbatim (adapt branch + filter to `main`): run `./gradlew rat`, upload the HTML report artifact, inject the + report into the job summary. +- Later/optional, mirroring grails-core: `codeql.yml`, dependabot config. + +## Phase 6 — GitHub Actions: ASF release & Marketplace publishing + +One commit: "ASF Compliance - release workflow". Modeled on grails-core's `release.yml` +and the `apache/grails-github-actions` composite actions, adapted because the deliverable +is a plugin ZIP, not Maven artifacts (no Nexus staging at all): + +ASF policy framing: the **official release is the signed source archive**, voted on by +the PMC and published to `dist.apache.org`; the plugin ZIP on JetBrains Marketplace is a +convenience binary. + +- Trigger: GitHub `release` event (tag `vX.Y.Z`), same as grails-core. +- Jobs: + 1. `publish` — build: `apache/grails-github-actions/pre-release@asf` (sets release + version), `./gradlew buildPlugin`, JetBrains `signPlugin` (Marketplace code-signing + chain: `CERTIFICATE_CHAIN`, `PRIVATE_KEY`, `PRIVATE_KEY_PASSWORD` secrets), attach + ZIP to the GitHub release (`softprops/action-gh-release` @ pinned SHA). + 2. `source` — produce the source distribution (git-archive based zip excluding + `.github` etc.), generate `sha512`, GPG-sign (`.asc`) with the release manager's key + from the Grails `KEYS` file. + 3. `upload` — `needs: [publish, source]`; svn-commit source + convenience artifacts to + `dist.apache.org/repos/dist/dev/grails/` for the vote. + 4. `release` — `environment: release` (required reviewers from `.asf.yaml`; this is the + post-vote gate): move dev → `dist/release`, then `./gradlew publishPlugin` to + JetBrains Marketplace (`PUBLISH_TOKEN` / `ORG_GRADLE_PROJECT_...PublishingToken` + secret — the listing is already transferred, so this just needs a token minted + under the ASF-controlled Marketplace account and stored as a repo secret). + 5. `close` — `apache/grails-github-actions/post-release@asf` (bump to next snapshot, + housekeeping). +- Secrets to provision (INFRA/PMC): GPG signing key material, `GRAILS_GHTOKEN`-style + token if needed, JetBrains Marketplace token + code-signing certs, Develocity key + (optional). +- Also add `release-abort`/`release-notes` style helpers later if useful; not required + for the first release. + +## Phase 7 — Artifact compliance + +Part of Phase 5/6 commits or separate: + +- Ensure `LICENSE` and `NOTICE` are packaged **inside the plugin ZIP / jar META-INF** + (grails-core gets this from its shared build-logic plugins; here add a small Gradle + convention — `processResources`/`buildPlugin` copy spec — in the root build). +- Optional stretch goals, ported from grails-core if the PMC wants parity: CycloneDX + SBOM (`SbomPlugin.groovy`-style `LICENSE_MAPPING` for transitives), reproducible-build + config (stable timestamps/ordering in the ZIP), `verify.sh`-style release verification + scripts. Not blockers for the first compliant release. + +## Phase 8 — Verify + +- [ ] `./gradlew rat` passes locally and in CI; every exclude justified in a comment. +- [ ] Header audit grep: no non-excluded source file lacks the full Apache 2.0 header + (JetBrains-attributed on imported files, ASF on new files). +- [ ] No imported file carries ASF licensing language (grep for "Licensed to the Apache + Software Foundation" must only hit files authored post-import) — guard until the + software grant is recorded. +- [ ] `NOTICE` + `LICENSE` present at root **and** inside the built plugin ZIP. +- [ ] `.asf.yaml` valid (notifications, branch protection, release environment). +- [ ] `./gradlew check buildPlugin verifyPlugin` green in CI on a PR. +- [ ] Snapshot publish job gated on build+verify via `needs:` and `if:` result guards. +- [ ] All workflow actions pinned to commit SHAs. +- [ ] Dry-run the release workflow to a test tag (skip the `release` environment job). +- [ ] PMC sign-off on: pre-grant header posture (JetBrains-attributed Apache 2.0), + plugin id/Marketplace transfer, testdata JARs in the source release. + +## Phase 9 — Post-grant follow-up (BLOCKED until software grant recorded) + +Do **not** start any of this until the JetBrains software grant is recorded with the +ASF Secretary and IP clearance is filed (incubator IP-clearance form, even for a +non-incubating PMC receiving a codebase). + +- Wholesale replacement of the JetBrains-attributed Apache 2.0 headers with the + canonical ASF header from `HEADER` (script in `etc/bin/`, one mechanical commit — + the former "option A"). Run in the legacy repo too if the fork has happened. +- Move the JetBrains copyright into `NOTICE` and add the "donated to the ASF" wording, + confirmed against the grant text. +- Update the Phase 8 grep guard: the ASF header becomes required on all non-excluded + files; the JetBrains-attributed form should no longer appear. +- Revisit RAT config: no changes expected (both header forms are Apache-2.0 to RAT), + but re-run the audit after the replacement. + +## Suggested commit sequence + +1. `ASF Compliance - license headers` (expand JetBrains headers to full Apache 2.0 — + done in working tree + commit the `etc/bin` expansion script) +2. `ASF Compliance - repository metadata` (NOTICE, LICENSE rename, .asf.yaml, README, + HEADER) +3. `ASF Compliance - RAT license audit` (rat plugin + config, passing) +4. `ASF Compliance - plugin identity` (vendor, groups) +5. `ASF Compliance - CI workflows` (gradle.yml, rat.yml) +6. `ASF Compliance - release workflow` (release.yml, packaging LICENSE/NOTICE) +7. *(post-grant, Phase 9)* `ASF Compliance - ASF license headers` (replacement + NOTICE + update)
