@xpertss/projen-types 0.0.17 → 0.0.19

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (32) hide show
  1. package/.jsii +20 -20
  2. package/README.md +99 -5
  3. package/lib/actions/action-build-workflow.js +1 -1
  4. package/lib/actions/action-dogfood-workflow.js +1 -1
  5. package/lib/actions/action-sonar-workflow.js +1 -1
  6. package/lib/actions/github-action-project.js +4 -3
  7. package/lib/cdk/app-runtime-scaffold.js +1 -1
  8. package/lib/cdk/cdk-app-project.js +1 -1
  9. package/lib/cdk/cdk-infra-project.js +1 -1
  10. package/lib/cdk/cdk-typescript-base.js +1 -1
  11. package/lib/cdk/components/ecr-ecs-constructs.js +1 -1
  12. package/lib/cdk/components/edge-networking-constructs.js +1 -1
  13. package/lib/cdk/database-component.js +1 -1
  14. package/lib/common/license-file.d.ts +10 -0
  15. package/lib/common/license-file.js +72 -0
  16. package/lib/common/projen-drift-check-workflow.js +1 -1
  17. package/lib/common/workflow-change-notice-workflow.js +1 -1
  18. package/lib/java/components/cdk-deploy-hook.js +1 -1
  19. package/lib/java/components/code-index-workflow.js +1 -1
  20. package/lib/java/components/docker-publish.js +1 -1
  21. package/lib/java/components/flyway-migration.js +1 -1
  22. package/lib/java/components/github-packages-publish.js +1 -1
  23. package/lib/java/components/maven-central-publish.js +1 -1
  24. package/lib/java/components/maven-upgrade-report.js +1 -1
  25. package/lib/java/java-app-project.js +1 -1
  26. package/lib/java/java-library-project.js +1 -1
  27. package/lib/java/java-maven-base.js +4 -3
  28. package/lib/java/java-service-project.js +1 -1
  29. package/lib/java/java-spring-boot-project.js +1 -1
  30. package/lib/java/maven/maven-module.js +1 -1
  31. package/lib/java/maven/maven-pom.js +1 -1
  32. package/package.json +1 -1
package/.jsii CHANGED
@@ -106,7 +106,7 @@
106
106
  },
107
107
  "name": "@xpertss/projen-types",
108
108
  "readme": {
109
- "markdown": "# @xpertss/projen-types\n\nProjen project types for CDK/TypeScript and Java/Maven projects.\n\nInstead of hand-maintaining `pom.xml`, `cdk.json`, and GitHub workflows, you declare a project type in a `.projenrc.ts` file and let [projen](https://github.com/projen/projen) generate (and keep up to date) the whole scaffold: build files, source skeletons, CI workflows, and deploy pipelines.\n\n## Project types at a glance\n\n| Type | What it is | Publishes to | Generated workflows |\n| --- | --- | --- | --- |\n| `CdkInfraProject` | Pure-infrastructure CDK stacks (CloudFront, Route53, SQS, API Gateway, Cognito, ECR/ECS for externally-built images) | - | `build` (PR checks), `deploy` (manual dispatch) |\n| `CdkAppProject` | Full TypeScript service behind API Gateway: infra + app source + database | - | `build`, `deploy`, `app-build` (PR checks) |\n| `JavaMavenProject` | Plain Maven project, single- or multi-module, no framework | - | `build`, `upgrade` (nightly report) |\n| `JavaLibraryProject` | Reusable Java library | Maven Central | `build`, `upgrade` (nightly report), `publish-maven-central`, `codeindex` |\n| `JavaAppProject` | GUI/TUI/CLI Java application | GitHub Packages | `build`, `upgrade` (nightly report), `publish-ghpackages` |\n| `JavaSpringBootProject` | Spring Boot on Maven, single- or multi-module, **no Docker** | - | `build`, `upgrade` (nightly report) |\n| `JavaServiceProject` | Spring Boot service deployed as a container | Docker Hub | `build`, `upgrade` (nightly report), `publish-docker`, `deploy-cdk` |\n| `GitHubActionProject` | Reusable GitHub Action or Workflow | GitHub Releases | `build`, `test-dogfood`, `sonar`, `release` |\n\nThe Java types are layered, so pick the lowest layer that does what you need:\n\n```text\nJavaMavenProject java_maven Maven only; single- or multi-module; any supported Java line\n├── JavaLibraryProject java_library + Maven Central publish, source/javadoc jars, code index\n├── JavaAppProject java_app + GitHub Packages publish\n└── JavaSpringBootProject java_spring_boot + Spring Boot BOM and executable-jar repackaging; no Docker/Flyway/CDK\n └── JavaServiceProject java_service + Docker publish, Flyway, CDK deploy hook (single-module)\n```\n\nEvery Java type targets a configurable Java line (`javaVersion`: `1.8`, `17`, `21` or `25`; see [Java version support](#java-version-support)), and every one except `JavaServiceProject` can be a multi-module Maven reactor (see [JavaMavenProject](#javamavenproject)).\n\nOne foundation class is also exported for advanced use: `CdkTypescriptProject` (shared CDK + TypeScript base for the CDK types).\n\nAll project types:\n\n- run a **drift check** in PR builds - a job that re-runs projen and fails if generated files were hand-edited. Edit `.projenrc.ts`, then run `npx projen`; never edit generated files directly.\n- use the GitHub secret `PROJEN_GITHUB_TOKEN` (a fine-grained PAT) for projen's automation. Override with `gheTokenSecret`.\n- make all publishing/deploying **manual** (workflow_dispatch) rather than on every merge.\n\n## Getting started\n\nScaffold the repo with projen's own bootstrap, pointed at this package:\n\n```bash\nmkdir my-project && cd my-project\ngit init\nnpx projen new --from @xpertss/projen-types cdk_infra --name my-project\n```\n\nThat writes a starter `.projenrc.ts`, synthesizes the whole scaffold and\ninstalls dependencies. The type names `projen new` accepts are `cdk_infra`,\n`cdk_app`, `java_maven`, `java_library`, `java_app`, `java_spring_boot`,\n`java_service` and `git_hub_action`; pass a bogus one to have it list them.\nRequired options become flags: `--name` for every type, plus\n`--group-id`/`--artifact-id` (Java) and `--sonar-host-url`\n(`git_hub_action`). Any other plainly-typed option can be passed the same\nway - `--java-version 1.8`, `--cdk-deploy-target-repo owner/repo`,\n`--docker-registry ghcr.io`, `--no-use-flyway`, and so on. Modules can't be\npassed on the command line; add them to `.projenrc.ts` afterwards (see\n[JavaMavenProject](#javamavenproject)).\n\n**Adding projen to a repo that already has content.** `projen new` runs\n`git init` and commits the scaffold by default. In a repo with existing work,\npass `--no-git` so it writes the files and leaves committing to you:\n\n```bash\ncd existing-repo\nnpx projen new --from @xpertss/projen-types java_spring_boot --name obeya --group-id org.xpertss.obeya --artifact-id obeya-parent --no-git\n```\n\n**Commit a lockfile.** The generated `build.yml` and drift check install the\ntoolchain with `npm ci`, which needs a committed `package-lock.json`, and\n`projen new` doesn't always leave one behind. Run `npm install` once and\ncommit `package-lock.json` along with the scaffold. Without it those\nworkflows fail with an `::error::` that says exactly this. For the Java\ntypes, `package.json` is generated, and it pins `projen` and\n`@xpertss/projen-types` to **exact** versions (no `^`), so every machine and\nCI run uses the same generator. A floating range would make the drift check\nreport the differences between generator versions as drift.\n\nCommit the result. From then on, every change to the scaffold goes through\n`.projenrc.ts` followed by `npx projen`:\n\n```bash\nnpx projen\n```\n\nEvery type scaffolds from that one command; no option is *required* that\n`projen new` cannot pass. The structured options - `environments` and\n`GitHubActionProject`'s `dogfood` - are ones projen's CLI cannot render\ninto a projenrc, so they are added afterwards by editing `.projenrc.ts` and\nre-running `npx projen`. Leaving `environments` out simply generates no\ndeploy workflow; leaving `dogfood` out (or a service's\n`cdkDeployTargetRepo`) still generates the workflow, with one step that\nfails and tells you what to add - a gate this package considers load-bearing\nis allowed to be missing loudly, never silently.\n\nThe `name` option must match the `name` field in the project's `package.json` (for the CDK types). For the Java types it becomes the root pom's `<name>` and the generated `package.json` name.\n\n> **Never write projen's generated-file marker text literally in `.projenrc.ts`.** projen deletes, as an orphaned generated file, any file containing the marker line (`~~ Generated by projen. To modify, ...`), and that includes `.projenrc.ts` itself. When you generate a file yourself (for example with `TextFile`), insert the marker through the file's `marker` property: ``file.addLine(`# ${file.marker}`)``.\n\n## Updating an existing project when this package changes\n\nYour generated scaffold reflects the *installed* version of `@xpertss/projen-types` - `npx projen` reads your project type from the package in `node_modules`, not from this repository. So when this package ships a change (a bug fix, a new generated file, or altered workflow behavior), an existing project picks it up by bumping the dependency and re-synthesizing. A stale `node_modules` silently regenerates with the old behavior, so the bump is the load-bearing step.\n\nUsing the [`auto-commit` action](#githubactionproject) as a running example, say a new release adds the `# Purpose:` workflow header and SonarCloud wording. In the `auto-commit` repo:\n\n```bash\nnpm view @xpertss/projen-types version # what's the newest release?\nnpm i -D @xpertss/projen-types@latest # bump the installed project type\nnpx projen # re-synthesize every generated file\ngit diff # review before committing\n```\n\nThe diff here is the workflows gaining a `# Purpose:` comment and `sonar.yml` reflecting the SonarCloud wording. Commit the result with a normal `chore:` or `fix:` message - you never hand-edit the generated files themselves.\n\n## Examples\n\n### CdkInfraProject\n\nPure infrastructure stacks with optional ECR/ECS and edge-networking constructs.\n\nScaffold it with the `cdk_infra` type:\n\n```bash\nnpx projen new --from @xpertss/projen-types cdk_infra --name media-edge-infra\n```\n\n```typescript\n// .projenrc.ts\nimport { CdkInfraProject } from '@xpertss/projen-types';\n\nconst project = new CdkInfraProject({\n name: 'media-edge-infra',\n environments: [\n 'dev',\n { name: 'stage', accountId: '111111111111', region: 'eu-central-1' },\n { name: 'prod', accountId: '222222222222', region: 'eu-central-1', requiresApproval: true },\n ],\n edgeResources: ['cloudfront', 'route53', 'sqs'],\n ecrEcs: { enabled: true, externalImageSource: true },\n slackWebhookSecret: 'SLACK_DEPLOY_WEBHOOK',\n});\n\nproject.synth();\n```\n\nYou get:\n\n- `cdk.json`, `cdk synth` / `cdk diff` / `cdk deploy` tasks, and the standard `AwsCdkTypeScriptApp` layout (CDK 2.189.1 by default).\n- `.github/workflows/build.yml` - PR build that hard-fails on projen drift.\n- `.github/workflows/deploy.yml` - manual dispatch with one `deploy-<env>` job per environment (runs `cdk deploy --all` with `--context environment=<env>`); `requiresApproval` environments get a GitHub Environment approval gate; a Slack notification step is added when `slackWebhookSecret` is set.\n- `src/constructs/edge-networking.ts` - helper constructs only for the requested `edgeResources` (`cloudfront`, `route53`, `apigateway`, `cognito`, `sqs`).\n- `src/constructs/ecr-ecs.ts` when `ecrEcs.enabled` - ECR repo + Fargate service; `externalImageSource: true` (default) means the service pulls an image built outside this repo.\n\n### CdkAppProject\n\n`CdkInfraProject` plus application source, a database construct, and an app-level build workflow.\n\nScaffold it with the `cdk_app` type:\n\n```bash\nnpx projen new --from @xpertss/projen-types cdk_app --name video-api\n```\n\n```typescript\n// .projenrc.ts\nimport { CdkAppProject } from '@xpertss/projen-types';\n\nconst project = new CdkAppProject({\n name: 'video-api',\n environments: ['dev', 'prod'],\n edgeResources: ['apigateway'],\n database: { engine: 'postgres', migrationTool: 'flyway' },\n appEntryPoint: 'src/app.ts',\n});\n\nproject.synth();\n```\n\nEverything from `CdkInfraProject`, plus:\n\n- `src/app.ts` - application entrypoint stub (`export function handler()`).\n- `src/handlers/example.ts` - API Gateway proxy handler stub, with `@types/aws-lambda` added as a dev dependency.\n- `src/constructs/database.ts` - a `Database` construct stub for the chosen engine (`postgres`/`mysql` -> RDS, `dynamodb` -> DynamoDB). `migrationTool` (no default, intentionally) is added as a dev dependency and referenced in the stub - wiring it up is left to you.\n- `.github/workflows/app-build.yml` - runs the project's test task on every PR, then checks for projen drift.\n\n### JavaMavenProject\n\nA plain Maven build with no framework and no publish target. It's the base of every other Java type, and it's usable on its own. It's single-module until you add a module, then it's a multi-module reactor.\n\nScaffold it with the `java_maven` type:\n\n```bash\nnpx projen new --from @xpertss/projen-types java_maven --name legacy-tools --group-id org.xpertss --artifact-id legacy-tools --java-version 1.8\n```\n\n**Single module:**\n\n```typescript\n// .projenrc.ts\nimport { JavaMavenProject } from '@xpertss/projen-types';\n\nconst project = new JavaMavenProject({\n name: 'legacy-tools',\n groupId: 'org.xpertss',\n artifactId: 'legacy-tools',\n javaVersion: '1.8',\n});\n\nproject.addDependency('commons-io/commons-io@2.20.0'); // exact version\nproject.addTestDependency('org.assertj/assertj-core@3.27.3');\n\nproject.synth();\n```\n\n**Multi-module:** each `addModule()` call adds a module directory with its own generated `pom.xml`. The root pom becomes the `pom`-packaged reactor parent. Everything stays one projen project, with one `.projen/`, one `.gitignore` and one `tasks.json`.\n\n```typescript\n// .projenrc.ts\nimport { JavaMavenProject } from '@xpertss/projen-types';\n\nconst project = new JavaMavenProject({\n name: 'toolkit',\n groupId: 'org.xpertss.toolkit',\n artifactId: 'toolkit-parent',\n version: '0.1.0-SNAPSHOT',\n javaVersion: '17',\n});\n\nconst model = project.addModule({ dir: 'toolkit-model', artifactId: 'toolkit-model', description: 'Domain model' });\nconst stub = project.addModule({ dir: 'tools/stub-model', artifactId: 'toolkit-stub-model' }); // nested dirs are fine\nconst cli = project.addModule({ dir: 'toolkit-cli', artifactId: 'toolkit-cli' });\n\nstub.addModuleDependency(model); // sibling module: versionless\ncli.addModuleDependency(model);\ncli.addDependency('info.picocli/picocli@4.7.7'); // pinned: the version moves to the parent\ncli.addDependency('org.yaml/snakeyaml'); // versionless: managed below\nproject.addManagedDependency('org.yaml/snakeyaml@2.4');\nproject.addBom('org.testcontainers/testcontainers-bom@1.21.3');\n\nproject.synth();\n```\n\nWhat the reactor looks like:\n\n- **Root `pom.xml`:**\n - `<packaging>pom</packaging>`, with `<modules>` in `addModule` order.\n - `<dependencyManagement>` holds the BOM imports first (`type=pom`, `scope=import`), then every module at `${project.version}`, then every pinned version.\n - `<pluginManagement>` holds the plugin versions, and versionless `<plugins>` are inherited by every module: compiler, surefire, failsafe, jar, and enforcer.\n - `junit-jupiter` is a test dependency every module inherits. Its version comes from `junit-bom`, or from the Spring Boot BOM in the Spring Boot types.\n- **Module `pom.xml`:** `<parent>` with the right `relativePath` (`tools/stub-model` → `../../pom.xml`), then `artifactId`, `name`, `description`, and versionless `<dependencies>`. Nothing else, unless the module adds plugins.\n- A version given to `module.addDependency`/`addTestDependency`/`addPlugin` is moved into the parent's `<dependencyManagement>`/`<pluginManagement>`, so every module that uses an artifact gets the same version. Pinning one artifact at two different versions fails the synth.\n- The synth fails with a clear message on:\n - two modules with the same `artifactId`\n - two modules with the same `dir`, or one `dir` nested inside another\n - a `dir` outside the repo\n - a module depending on itself\n - `packaging` set to anything but `pom` on a project with modules\n\nYou get:\n\n- the root `pom.xml` (and one per module), written by this package:\n - **exact versions only**: a range such as `^1`, `~1.2` or `[1,2)` fails the synth\n - the compiler level, enforcer rule and JUnit line all follow `javaVersion`\n- one Maven run per build. `npx projen build` synthesizes, then runs `mvn -B verify`: compile, unit tests (surefire), package, and `*IT` integration tests (failsafe). There is no `mvn deploy` and no `dist/`.\n- `.github/workflows/build.yml`: a PR build that installs the pinned Node toolchain (`npm ci`) and the project's JDK (`actions/setup-java`, Maven cache), then runs `npx projen build`, plus a SonarQube step when `sonarProjectKey` is set. See [Adding CI jobs](#adding-ci-jobs-to-a-java-project).\n- `.github/workflows/upgrade.yml`: a nightly (03:00 UTC) **report** of available dependency and plugin updates in the job summary. It changes nothing, because every version comes from `.projenrc.ts`; apply an update there. Turn it off with `upgradeWorkflow: false`. The same report runs locally with `npx projen upgrade`.\n- the projen drift check, `LICENSE` (MIT by default), `.editorconfig`, and a generated `package.json` that pins the projen toolchain exactly.\n- no sample code, unless `sample: true` on a single-module project.\n\n### JavaLibraryProject\n\nA reusable Java library published to Maven Central.\n\nScaffold it with the `java_library` type:\n\n```bash\nnpx projen new --from @xpertss/projen-types java_library --name common-utils --group-id org.xpertss --artifact-id common-utils\n```\n\n```typescript\n// .projenrc.ts\nimport { JavaLibraryProject } from '@xpertss/projen-types';\n\nconst project = new JavaLibraryProject({\n name: 'common-utils',\n groupId: 'org.xpertss',\n artifactId: 'common-utils',\n version: '1.0.0',\n sonarProjectKey: 'org.xpertss:common-utils',\n mavenCentralOidc: true,\n});\n\nproject.synth();\n```\n\nYou get everything from [`JavaMavenProject`](#javamavenproject), so modules work here too, plus:\n\n- `maven-source-plugin` and `maven-javadoc-plugin`, attaching the `-sources`/`-javadoc` jars Maven Central requires. In a multi-module library every module attaches them.\n- `.github/workflows/publish-maven-central.yml` - manual dispatch running `mvn -B deploy -P release`. With `mavenCentralOidc: true` it uses Maven Central's OIDC trusted publishing (no GPG secrets needed); otherwise it expects the secrets `MAVEN_GPG_PRIVATE_KEY`, `MAVEN_GPG_PASSPHRASE`, `MAVEN_CENTRAL_USERNAME`, `MAVEN_CENTRAL_PASSWORD`.\n- `.github/workflows/codeindex.yml` - on push to `main`, generates a Java source index under `.cai/` covering every module (disable with `publishCodeIndex: false`).\n\n### JavaSpringBootProject\n\nSpring Boot on Maven, single- or multi-module, **without** Docker, Flyway or a deploy hook. It's [`JavaMavenProject`](#javamavenproject) plus Spring Boot dependency management. Use it for a Spring Boot repo that ships something other than this package's container image, such as a multi-module control plane, or for a Boot app you deploy your own way.\n\nScaffold it with the `java_spring_boot` type:\n\n```bash\nnpx projen new --from @xpertss/projen-types java_spring_boot --name obeya --group-id org.xpertss.obeya --artifact-id obeya-parent\n```\n\n```typescript\n// .projenrc.ts\nimport { JavaSpringBootProject } from '@xpertss/projen-types';\n\nconst project = new JavaSpringBootProject({\n name: 'obeya',\n groupId: 'org.xpertss.obeya',\n artifactId: 'obeya-parent',\n version: '0.1.0-SNAPSHOT',\n javaVersion: '21',\n copyrightOwner: 'Xpert Software',\n});\n\n// plain jar modules: shared libraries, clients, tools\nconst model = project.addModule({ dir: 'obeya-model', artifactId: 'obeya-model' });\nconst client = project.addModule({ dir: 'obeya-api-client', artifactId: 'obeya-api-client' });\nclient.addModuleDependency(model);\n\n// a Spring Boot application module, repackaged into an executable jar\nconst server = project.addSpringBootModule({ dir: 'obeya-server', artifactId: 'obeya-server' });\nserver.addModuleDependency(model);\nserver.addDependency('org.springframework.boot/spring-boot-starter-web'); // versionless: Boot's BOM manages it\nserver.addTestDependency('org.springframework.boot/spring-boot-starter-test');\n\nproject.addBom('org.testcontainers/testcontainers-bom@1.21.3');\n\nproject.synth();\n```\n\nYou get everything from [`JavaMavenProject`](#javamavenproject), plus:\n\n- `spring-boot-dependencies` imported as the **first** BOM, so starters and every library Boot manages (JUnit included) are added without a version. `springBootVersion` sets it; the default is the newest release of the line that supports `javaVersion` (see [Java version support](#java-version-support)).\n- `spring-boot-maven-plugin`, versioned in `<pluginManagement>`:\n - In a **single-module** project the root jar is repackaged into an executable jar.\n - In a **multi-module** project only modules added with `addSpringBootModule()` are repackaged; `addModule()` modules stay plain jars.\n - A repackaged module needs its `@SpringBootApplication` class before `mvn verify` passes, because `repackage` fails with `Unable to find main class` until one exists. Add a module as a plain `addModule()` while it's still empty.\n- failsafe configured to run integration tests against the compiled classes rather than the repackaged jar (as `spring-boot-starter-parent` does).\n- no starters, no Docker, no Flyway, no CDK. Add the starters you need with `addDependency`.\n\n### JavaServiceProject\n\nA Spring Boot service that publishes a Docker image and can trigger deploys in a companion CDK repo. It is [`JavaSpringBootProject`](#javaspringbootproject) plus Docker, Flyway and a deploy hook. It is **single-module**, because the Docker build, the migrations and the deploy hook all assume one deployable at the repo root; `addModule()` fails and points you to `JavaSpringBootProject`.\n\nScaffold it with the `java_service` type:\n\n```bash\nnpx projen new --from @xpertss/projen-types java_service --name stream-processor --group-id org.xpertss --artifact-id stream-processor\n```\n\n```typescript\n// .projenrc.ts\nimport { JavaServiceProject } from '@xpertss/projen-types';\n\nconst project = new JavaServiceProject({\n name: 'stream-processor',\n groupId: 'org.xpertss',\n artifactId: 'stream-processor',\n dockerRegistry: 'docker.io/xpertss',\n cdkDeployTargetRepo: 'xpertss/stream-infra',\n environments: ['dev', { name: 'prod', requiresApproval: true }],\n});\n\nproject.synth();\n```\n\nYou get everything from [`JavaSpringBootProject`](#javaspringbootproject) (and so from `JavaMavenProject`), plus:\n\n- `spring-boot-starter-web` added to the pom (versionless; Boot's BOM manages it).\n- `.github/workflows/publish-docker.yml` - manual dispatch: `mvn -B package && docker build -t <registry>/<name>:<sha>`, logged in with the `DOCKER_USERNAME` / `DOCKER_PASSWORD` secrets. `dockerRegistry` defaults to `docker.io`.\n- Flyway wiring when `useFlyway` (default `true`): `flyway-core` (versionless, so it matches what Boot's Flyway auto-configuration expects), `flyway-maven-plugin` pinned for the Java line, and `src/main/resources/db/migration/V1__init.sql`.\n- `.github/workflows/deploy-cdk.yml` (the `CdkDeployHook`, generated by default) - manual dispatch with an environment selector; each job sends a `workflow_dispatch` to `deploy.yml` in the companion `cdkDeployTargetRepo` (a `CdkInfraProject`/`CdkAppProject` repo). With no `cdkDeployTargetRepo` set, the workflow is still generated but each job's only step fails with instructions - it is dispatch-only, so that lands on whoever tries to deploy rather than on every PR. Turn the workflow off entirely with `cdkDeployHook: false`. `environments` defaults to `['prod']`.\n\n### JavaAppProject\n\nA GUI/TUI/CLI Java application published to GitHub Packages only - no Maven Central, no Docker, no CDK deploy hook.\n\nScaffold it with the `java_app` type:\n\n```bash\nnpx projen new --from @xpertss/projen-types java_app --name studio-cli --group-id org.xpertss --artifact-id studio-cli\n```\n\n```typescript\n// .projenrc.ts\nimport { JavaAppProject } from '@xpertss/projen-types';\n\nconst project = new JavaAppProject({\n name: 'studio-cli',\n groupId: 'org.xpertss',\n artifactId: 'studio-cli',\n ghPackagesRegistry: 'https://maven.pkg.github.com/xpertss/studio-cli',\n});\n\nproject.synth();\n```\n\nYou get everything from [`JavaMavenProject`](#javamavenproject) (modules included), plus `.github/workflows/publish-ghpackages.yml` - manual dispatch running `mvn -B deploy -DaltDeploymentRepository=github::<registry>`, authenticated with `GITHUB_TOKEN`. `ghPackagesRegistry` defaults to `https://maven.pkg.github.com/<repo>` derived from the repository URL.\n\n### GitHubActionProject\n\nA reusable GitHub Action or Workflow. This example scaffolds an action that stages a folder and, only if it changed, commits and pushes it.\n\nScaffold it with the `git_hub_action` type:\n\n```bash\nnpx projen new --from @xpertss/projen-types git_hub_action --name auto-commit --sonar-host-url https://sonarcloud.io\n```\n\n```typescript\n// .projenrc.ts\nimport { GitHubActionProject } from '@xpertss/projen-types';\n\nconst project = new GitHubActionProject({\n name: 'auto-commit',\n description: 'Stage a folder and, only if it changed, commit and push it',\n sonarHostUrl: 'https://sonarcloud.io', // required, no default - your SonarCloud URL\n dogfood: {\n // Two scenario steps: the \"changed\" path and the \"no-op\" path are both\n // load-bearing behavior for this action (a double-commit or a\n // push-when-empty bug is the failure mode a hand-rolled inline-shell\n // alternative is most likely to introduce).\n scenario: [\n {\n name: 'Changed path',\n fixtureSteps: [\n 'echo \"$(date -u +%Y%m%dT%H%M%SZ)\" >> test/fixtures/dogfood-state.txt',\n ],\n inputs: {\n commit_message: 'test: dogfood',\n branches: 'test/dogfood',\n },\n assertions: [\n // step id defaults to a slug of `name` - here \"changed-path-0\"\n '[ \"${{ steps.changed-path-0.outputs.committed }}\" = \"true\" ]',\n ],\n },\n {\n // No fixture change this time - nothing new to commit.\n name: 'No-op path',\n id: 'no-op-check', // pin an explicit id instead of relying on the default slug\n inputs: {\n commit_message: 'test: dogfood',\n branches: 'test/dogfood',\n },\n assertions: [\n '[ \"${{ steps.no-op-check.outputs.committed }}\" = \"false\" ]',\n ],\n },\n ],\n cleanup: [\n 'git push origin --delete test/dogfood || true',\n ],\n },\n});\n\nproject.synth();\n```\n\nYou get:\n\n- `action.yml` and `auto-commit.sh` are hand-written - this type only lints their content via `build.yml`'s shellcheck/yamllint/actionlint checks.\n- `.github/workflows/build.yml` - lint gate: `apt`-installed shellcheck/yamllint plus a pinned, SHA-256-verified `actionlint` release binary. Gates `main` alongside `sonar.yml`.\n- `.github/workflows/test-dogfood.yml` - runs the `dogfood.scenario` steps above against this repo's own `action.yml` (via `uses: ./`), then the shared `cleanup`, on `workflow_dispatch`, every `pull_request`, and nightly. Omit `dogfood` and the workflow still exists, with one step that fails on every PR until you declare a scenario - AD-001 allows a dogfood to be missing loudly, never silently. A *partial* `dogfood` (a scenario with no cleanup) is a synth error.\n- `.github/workflows/sonar.yml` - SonarCloud scan via the Scanner CLI (the quality gate blocks the PR), scanning `action.yml`/`.github/workflows/**`/`**/*.sh` explicitly.\n- `.github/workflows/release.yml` - `feat:`/`fix:` commits on `main` bump the version, tag `vX.Y.Z`, and create a GitHub Release.\n- `.github/workflows/projen-drift-check.yml` and `workflow-change-notice.yml` - drift detection and a change notice, always included.\n- `package.json` (**private**, version source only), `.yamllint`, `LICENSE` (MIT by default), and a `README.md` template - all regenerated by `npx projen`.\n\nNeeds the same two secrets as everything else in this package: `PROJEN_GITHUB_TOKEN` (used for automated PR comments) and `SONAR_TOKEN` (the Sonar scan). Onboard a brand-new action repo following [Getting started](#getting-started), write the `.projenrc.ts` above, then hand-write `action.yml`/`auto-commit.sh`/`test/fixtures/`.\n\n## Java version support\n\n`javaVersion` picks the Java line a Java project targets. It defaults to `21`; the supported lines are `1.8` (alias `8`), `17`, `21` and `25`, and any other value fails the synth with that list. **Nothing in the generated build is hard-coded to one Java line.** Everything that depends on it comes from one table in this package, grouped by line:\n\n| | `1.8` | `17` | `21` | `25` |\n| --- | --- | --- | --- | --- |\n| Compiler level | `maven.compiler.source`/`target` = `1.8` (javac 8 has no `--release`) | `maven.compiler.release` = `17` | `release` = `21` | `release` = `25` |\n| Enforcer `requireJavaVersion` | `[1.8,)` | `[17,)` | `[21,)` | `[25,)` |\n| JUnit (`junit-bom`) | 5.14.4 (JUnit 6 needs Java 17) | 6.1.3 | 6.1.3 | 6.1.3 |\n| Default Spring Boot (`JavaSpringBootProject`) | 2.7.18 (Boot 3+ needs Java 17; a 3.x+ `springBootVersion` fails the synth) | 4.1.1 | 4.1.1 | 4.1.1 |\n| `flyway-maven-plugin` (`JavaServiceProject`) | 9.22.3 | 13.9.0 | 13.9.0 | 13.9.0 |\n| CI JDK (`actions/setup-java` `java-version`) | `8` | `17` | `21` | `25` |\n\nMaven plugins (the same on every line, because all of them run on JDK 8): `maven-compiler-plugin` 3.16.0, `maven-surefire-plugin`/`maven-failsafe-plugin` 3.6.0, `maven-jar-plugin` 3.5.1, `maven-enforcer-plugin` 3.6.3, `maven-source-plugin` 3.4.0 and `maven-javadoc-plugin` 3.12.0 (library only), `versions-maven-plugin` 2.22.0 (the `upgrade` report).\n\n**The enforcer.** `maven-enforcer-plugin` fails the build at the start when the JDK or Maven running it is older than the project needs. Without it you get confusing compiler errors later, or a jar built for the wrong runtime. Its Java rule always follows `javaVersion`. The Maven rule defaults to `[3.9,)`; change it with `minMavenVersion`, or drop the plugin entirely with `enforcer: false`. CI installs the matching JDK through `setup-java`, so the rule only fires on a developer machine with the wrong JDK. A Java 1.8 project builds on any newer JDK (the rule is a minimum), but the JDK 8 runtime API is guaranteed only when you build on JDK 8.\n\n**Overriding a version.** `pluginVersions` replaces any default in the table, keyed by `groupId/artifactId`, with an exact version:\n\n```typescript\nnew JavaMavenProject({\n name: 'svc',\n groupId: 'org.xpertss',\n artifactId: 'svc',\n pluginVersions: {\n 'org.apache.maven.plugins/maven-surefire-plugin': '3.5.6',\n 'org.junit/junit-bom': '5.14.4',\n },\n});\n```\n\n`project.pinnedVersion('org.apache.maven.plugins/maven-surefire-plugin')` returns the version in effect, for reuse in your own plugin config.\n\n**New Java lines** are one new row in this package's version table and a release. Until a line is listed, `javaVersion` rejects it rather than guessing.\n\n## Adding CI jobs to a Java project\n\n`build.yml` is exposed as `project.buildVerifyWorkflow`, so jobs are added through projen's `addJob` rather than `addOverride`. `project.ciSetupSteps` holds the steps every Maven job here needs: pinned Node, `npm ci`, and the project's JDK with a Maven cache. Every job in `build.yml` can then be made a required check on `main`.\n\n```typescript\nimport { github } from 'projen';\n\nproject.buildVerifyWorkflow.addJob('integration', {\n name: 'Container integration tests',\n runsOn: ['self-hosted', 'linux', 'fedora', 'podman'],\n permissions: { contents: github.workflows.JobPermission.READ },\n steps: [\n { name: 'Checkout', uses: 'actions/checkout@v7' },\n ...project.ciSetupSteps,\n { name: 'Integration tests', run: 'mvn -B verify -Pcontainer-its' },\n ],\n});\n```\n\n## Common options\n\nCDK project types (`CdkInfraProjectOptions` / `CdkAppProjectOptions`):\n\n| Option | Default | Description |\n| --- | --- | --- |\n| `name` | - (required) | Project name; must match `package.json` |\n| `cdkVersion` | `2.189.1` | AWS CDK version |\n| `gheTokenSecret` | `PROJEN_GITHUB_TOKEN` | GitHub secret holding projen's PAT |\n| `slackWebhookSecret` | - | GitHub secret with a Slack webhook URL for deploy notifications |\n| `environments` | - (no `deploy` workflow) | Deploy targets for the `deploy` workflow; strings or `EnvironmentOptions` |\n| `ecrEcs` | - | `EcrEcsOptions` - `enabled`, `externalImageSource` (default `true`) |\n| `edgeResources` | - | Subset of `cloudfront`, `route53`, `apigateway`, `cognito`, `sqs` |\n| `database` | - (app only) | `DatabaseOptions` - `engine` (`postgres`/`mysql`/`dynamodb`, default `postgres`), `migrationTool` |\n| `appEntryPoint` | `src/app.ts` (app only) | Path of the generated application entrypoint |\n\nJava project types (`JavaMavenProjectOptions` and everything built on it - `JavaLibraryProjectOptions`, `JavaAppProjectOptions`, `JavaSpringBootProjectOptions`, `JavaServiceProjectOptions`):\n\n| Option | Default | Description |\n| --- | --- | --- |\n| `name` | - (required) | Project name |\n| `groupId` | - (required) | Maven group id |\n| `artifactId` | - (required) | Maven artifact id (of the reactor parent, in a multi-module project) |\n| `version` | `0.1.0` | Maven version; must be exact |\n| `description` / `url` | - | Written to the root pom |\n| `javaVersion` | `21` | Java line: `1.8`, `17`, `21`, `25` - see [Java version support](#java-version-support) |\n| `javaDistribution` | `temurin` | `actions/setup-java` distribution for CI |\n| `packaging` | `jar` | Root packaging while the project has no modules; a project with modules is always `pom` |\n| `sample` | `false` | Starter `Main` + test under the `groupId` package (single-module only) |\n| `enforcer` | `true` | `maven-enforcer-plugin` with Maven and Java version rules |\n| `minMavenVersion` | `3.9` | The enforcer's `requireMavenVersion` minimum |\n| `pluginVersions` | - | Exact-version overrides for this package's defaults, keyed by `groupId/artifactId` |\n| `licensed` | `true` | Write a `LICENSE` |\n| `license` | `MIT` | SPDX identifier for the `LICENSE` |\n| `copyrightOwner` / `copyrightPeriod` | `xpertss` / current year | Named in the `LICENSE` |\n| `editorconfig` | `true` | Write a projen-managed `.editorconfig` (also on the CDK and action types) |\n| `upgradeWorkflow` | `true` | Generate the nightly update report (`upgrade.yml`) |\n| `sonarProjectKey` | - | SonarQube project key; the sonar step is skipped when unset |\n| `gheTokenSecret` | `PROJEN_GITHUB_TOKEN` | GitHub secret holding projen's PAT |\n| `springBootVersion` | newest Boot for `javaVersion` (Spring Boot types only) | Exact Spring Boot version: the BOM and `spring-boot-maven-plugin` |\n| `cdkDeployTargetRepo` | - (service only; `deploy-cdk.yml` fails until set) | Companion CDK repo (`owner/repo`) whose `deploy.yml` the deploy hook dispatches |\n| `cdkDeployHook` | `true` (service only) | Whether to generate `deploy-cdk.yml` at all |\n| `dockerRegistry` | `docker.io` (service only) | Registry the Docker image is pushed to |\n| `useFlyway` | `true` (service only) | Flyway plugin/dependency + `V1__init.sql` |\n\n`EnvironmentOptions` for deploy targets:\n\n```text\ninterface EnvironmentOptions {\n readonly name: string; // e.g. \"dev\", \"stage\", \"prod\"\n readonly accountId?: string; // AWS account id (CDK deploys)\n readonly region?: string; // AWS region (CDK deploys)\n readonly requiresApproval?: boolean; // GitHub Environment approval gate (default false)\n}\n```\n\nPlain strings (`'dev'`) are shorthand for `{ name: 'dev' }`.\n\n`GitHubActionProjectOptions`:\n\n| Option | Default | Description |\n| --- | --- | --- |\n| `name` | - (required) | Project name |\n| `description` | - | One-line description; used in the default README template and recorded in the private `package.json` |\n| `sonarHostUrl` | - (required) | URL of your SonarCloud instance (e.g. `https://sonarcloud.io`); must be reachable from github.com-hosted runners |\n| `sonarTokenSecret` | `SONAR_TOKEN` | GitHub secret holding the Sonar token |\n| `sonarPullRequestGate` | `true` | Whether `sonar.yml` also runs on `pull_request` as a pass/fail gate |\n| `dogfood` | - (a `test-dogfood.yml` that fails until you declare one) | `ActionDogfoodOptions` - the scenario that exercises the action end-to-end via `uses: ./` |\n| `license` | `MIT` | SPDX identifier for the generated `LICENSE` |\n| `gheTokenSecret` | `PROJEN_GITHUB_TOKEN` | GitHub secret holding projen's PAT |\n\n`ActionDogfoodOptions`/`ActionDogfoodStep` - the dogfood scenario (`test-dogfood.yml`):\n\n```text\ninterface ActionDogfoodOptions {\n readonly scenario: ActionDogfoodStep[]; // one or more, run in order - required\n readonly cleanup: string[]; // shared, run once at the end with `if: always()` - required\n}\n\ninterface ActionDogfoodStep {\n readonly name: string; // labels this step-group's generated workflow steps\n readonly id?: string; // step id for the `uses: ./` call; default: a slug of `name`\n readonly fixtureSteps?: string[]; // shell, before the invocation (default: none)\n readonly inputs?: Record<string, string>; // `with:` for this invocation (default: none)\n readonly assertions: string[]; // shell, after the invocation - required, job fails unless all exit 0\n}\n```\n\nMost actions need exactly one `scenario` step. Actions with a re-run/no-op/idempotency behavior to verify (e.g. `auto-commit`'s no-op-on-no-change path, `create-pull-request`'s reuse-the-PR path) declare two - the second typically omits `fixtureSteps` so its invocation sees no new state, and its assertion checks the opposite outcome of the first. Reference an invocation's own outputs from a later assertion via `${{ steps.<id>.outputs.<name> }}`, using either the default slug or an explicit `id`.\n\n## Required GitHub secrets\n\n| Secret | Used by | Notes |\n| --- | --- | --- |\n| `PROJEN_GITHUB_TOKEN` | all types | PAT for projen's self-mutation/automation; override via `gheTokenSecret` |\n| `SONAR_TOKEN` | Java types with `sonarProjectKey`; `GitHubActionProject` | SonarQube scan step in `build.yml` / `sonar.yml`; override via `sonarTokenSecret` on `GitHubActionProject` |\n| `DOCKER_USERNAME` / `DOCKER_PASSWORD` | `JavaServiceProject` | Docker image push |\n| `MAVEN_GPG_PRIVATE_KEY`, `MAVEN_GPG_PASSPHRASE`, `MAVEN_CENTRAL_USERNAME`, `MAVEN_CENTRAL_PASSWORD` | `JavaLibraryProject` without `mavenCentralOidc` | Not needed with OIDC trusted publishing |\n| (your Slack webhook secret) | CDK types with `slackWebhookSecret` | Deploy notifications |\n\n## Customizing `.gitignore`\n\nEvery project type already ignores JetBrains IDE state (`/.idea/*`) and the `/spec/` directory with all of its subdirectories (local plans and specs, never committed). The Java types also ignore Maven `target/` output and Eclipse files. To ignore more, call the inherited `addGitIgnore()` method after constructing the project in your `.projenrc.ts`:\n\n```typescript\n// .projenrc.ts\nimport { CdkInfraProject } from '@xpertss/projen-types';\n\nconst project = new CdkInfraProject({\n name: 'my-infra',\n});\n\nproject.addGitIgnore('*.log');\nproject.addGitIgnore('.env.*');\nproject.addGitIgnore('/build/');\n\nproject.synth();\n```\n\nThe same works for every type - just swap the class (e.g. `JavaServiceProject`, `GitHubActionProject`). Each call takes one standard gitignore glob pattern. There is no `gitignore` constructor option: the project types' option interfaces do not expose one, so patterns go through `addGitIgnore()` instead.\n\nRe-run `npx projen` after editing - `.gitignore` is a generated file, so hand-editing it is caught by the drift check.\n\n## Going further\n\nThe project types are composed from smaller components you can also attach to your own projects:\n\n| Component | Applies to | Purpose |\n| --- | --- | --- |\n| `AppRuntimeScaffold` | `NodeProject` | App source skeleton (`app.ts` + handlers) |\n| `DatabaseComponent` | `NodeProject` | Database construct stub + migration tool wiring |\n| `EcrEcsConstructs` | `Project` | ECR + Fargate ECS construct helper |\n| `EdgeNetworkingConstructs` | `Project` | Per-resource edge networking construct helpers |\n| `MavenPom` | `Project` | A `pom.xml` with modules, BOM imports, dependency/plugin management, and exact versions only |\n| `MavenModule` | `JavaMavenProject` | One module of a reactor (created by `addModule()`) |\n| `MavenUpgradeReport` | `GitHubProject` | Nightly report-only `upgrade.yml` |\n| `MavenCentralPublish` | `JavaMavenProject` | Manual-dispatch Maven Central publish workflow |\n| `DockerPublish` | `JavaMavenProject` | Manual-dispatch Docker build+push workflow |\n| `GitHubPackagesPublish` | `JavaMavenProject` | Manual-dispatch GitHub Packages publish workflow |\n| `FlywayMigration` | `JavaSpringBootProject` | Flyway plugin/dependency + migrations directory |\n| `CdkDeployHook` | `JavaMavenProject` | Manual-dispatch workflow that triggers `deploy.yml` in a companion CDK repo |\n| `CodeIndexWorkflow` | `JavaMavenProject` | Code index generation on push to `main` |\n| `ActionBuildWorkflow` | `GitHubProject` | The `lint` task (shellcheck/yamllint/pinned actionlint) + `build.yml` |\n| `ActionDogfoodWorkflow` | `GitHubProject` | `test-dogfood.yml` from an `ActionDogfoodOptions` scenario |\n| `ActionSonarWorkflow` | `GitHubProject` | `sonar.yml` (SonarCloud scan via the Scanner CLI) |\n\nExample - adding a Docker publish to a `JavaMavenProject` (`JavaServiceProject` is the packaged version of this, with Spring Boot):\n\n```typescript\nimport { DockerPublish, JavaMavenProject } from '@xpertss/projen-types';\n\nconst project = new JavaMavenProject({\n name: 'my-service',\n groupId: 'org.xpertss',\n artifactId: 'my-service',\n});\n\nnew DockerPublish(project, { dockerRegistry: 'ghcr.io' });\n\nproject.synth();\n```\n\nThe full API reference, including every option and property, is in [API.md](./API.md).\n"
109
+ "markdown": "# @xpertss/projen-types\n\nProjen project types for CDK/TypeScript and Java/Maven projects.\n\nInstead of hand-maintaining `pom.xml`, `cdk.json`, and GitHub workflows, you declare a project type in a `.projenrc.ts` file and let [projen](https://github.com/projen/projen) generate (and keep up to date) the whole scaffold: build files, source skeletons, CI workflows, and deploy pipelines.\n\n## Project types at a glance\n\n| Type | What it is | Publishes to | Generated workflows |\n| --- | --- | --- | --- |\n| `CdkInfraProject` | Pure-infrastructure CDK stacks (CloudFront, Route53, SQS, API Gateway, Cognito, ECR/ECS for externally-built images) | - | `build` (PR checks), `deploy` (manual dispatch) |\n| `CdkAppProject` | Full TypeScript service behind API Gateway: infra + app source + database | - | `build`, `deploy`, `app-build` (PR checks) |\n| `JavaMavenProject` | Plain Maven project, single- or multi-module, no framework | - | `build`, `upgrade` (nightly report) |\n| `JavaLibraryProject` | Reusable Java library | Maven Central | `build`, `upgrade` (nightly report), `publish-maven-central`, `codeindex` |\n| `JavaAppProject` | GUI/TUI/CLI Java application | GitHub Packages | `build`, `upgrade` (nightly report), `publish-ghpackages` |\n| `JavaSpringBootProject` | Spring Boot on Maven, single- or multi-module, **no Docker** | - | `build`, `upgrade` (nightly report) |\n| `JavaServiceProject` | Spring Boot service deployed as a container | Docker Hub | `build`, `upgrade` (nightly report), `publish-docker`, `deploy-cdk` |\n| `GitHubActionProject` | Reusable GitHub Action or Workflow | GitHub Releases | `build`, `test-dogfood`, `sonar`, `release` |\n\nThe Java types are layered, so pick the lowest layer that does what you need:\n\n```text\nJavaMavenProject java_maven Maven only; single- or multi-module; any supported Java line\n├── JavaLibraryProject java_library + Maven Central publish, source/javadoc jars, code index\n├── JavaAppProject java_app + GitHub Packages publish\n└── JavaSpringBootProject java_spring_boot + Spring Boot BOM and executable-jar repackaging; no Docker/Flyway/CDK\n └── JavaServiceProject java_service + Docker publish, Flyway, CDK deploy hook (single-module)\n```\n\nEvery Java type targets a configurable Java line (`javaVersion`: `1.8`, `17`, `21` or `25`; see [Java version support](#java-version-support)), and every one except `JavaServiceProject` can be a multi-module Maven reactor (see [JavaMavenProject](#javamavenproject)).\n\nOne foundation class is also exported for advanced use: `CdkTypescriptProject` (shared CDK + TypeScript base for the CDK types).\n\nAll project types:\n\n- run a **drift check** in PR builds - a job that re-runs projen and fails if generated files were hand-edited. Edit `.projenrc.ts`, then run `npx projen`; never edit generated files directly.\n- use the GitHub secret `PROJEN_GITHUB_TOKEN` (a fine-grained PAT) for projen's automation. Override with `gheTokenSecret`.\n- make all publishing/deploying **manual** (workflow_dispatch) rather than on every merge.\n\n## Getting started\n\nHow you start depends on what is already in the directory:\n\n| Starting point | Do this |\n| --- | --- |\n| Empty directory | `projen new --from` (below) |\n| Existing repo, no `.projenrc.ts` | `projen new --from ... --no-git` (below) |\n| A `.projenrc.ts` but no `.projen/` directory - copied from another repo, or one of the [examples](#examples) pasted into a fresh repo | [Starting from an existing `.projenrc.ts`](#starting-from-an-existing-projenrcts) |\n\nScaffold the repo with projen's own bootstrap, pointed at this package:\n\n```bash\nmkdir my-project && cd my-project\ngit init\nnpx projen new --from @xpertss/projen-types cdk_infra --name my-project\n```\n\nThat writes a starter `.projenrc.ts`, synthesizes the whole scaffold and\ninstalls dependencies. The type names `projen new` accepts are `cdk_infra`,\n`cdk_app`, `java_maven`, `java_library`, `java_app`, `java_spring_boot`,\n`java_service` and `git_hub_action`; pass a bogus one to have it list them.\nRequired options become flags: `--name` for every type, plus\n`--group-id`/`--artifact-id` (Java) and `--sonar-host-url`\n(`git_hub_action`). Any other plainly-typed option can be passed the same\nway - `--java-version 1.8`, `--cdk-deploy-target-repo owner/repo`,\n`--docker-registry ghcr.io`, `--no-use-flyway`, and so on. Modules can't be\npassed on the command line; add them to `.projenrc.ts` afterwards (see\n[JavaMavenProject](#javamavenproject)).\n\n**Adding projen to a repo that already has content.** `projen new` runs\n`git init` and commits the scaffold by default. In a repo with existing work,\npass `--no-git` so it writes the files and leaves committing to you:\n\n```bash\ncd existing-repo\nnpx projen new --from @xpertss/projen-types java_spring_boot --name obeya --group-id org.xpertss.obeya --artifact-id obeya-parent --no-git\n```\n\n**Commit a lockfile.** The generated `build.yml` and drift check install the\ntoolchain with `npm ci`, which needs a committed `package-lock.json`, and\n`projen new` doesn't always leave one behind. Run `npm install` once and\ncommit `package-lock.json` along with the scaffold. Without it those\nworkflows fail with an `::error::` that says exactly this. For the Java\ntypes, `package.json` is generated, and it pins `projen` and\n`@xpertss/projen-types` to **exact** versions (no `^`), so every machine and\nCI run uses the same generator. A floating range would make the drift check\nreport the differences between generator versions as drift.\n\nCommit the result. From then on, every change to the scaffold goes through\n`.projenrc.ts` followed by `npx projen`:\n\n```bash\nnpx projen\n```\n\nEvery type scaffolds from that one command; no option is *required* that\n`projen new` cannot pass. The structured options - `environments` and\n`GitHubActionProject`'s `dogfood` - are ones projen's CLI cannot render\ninto a projenrc, so they are added afterwards by editing `.projenrc.ts` and\nre-running `npx projen`. Leaving `environments` out simply generates no\ndeploy workflow; leaving `dogfood` out (or a service's\n`cdkDeployTargetRepo`) still generates the workflow, with one step that\nfails and tells you what to add - a gate this package considers load-bearing\nis allowed to be missing loudly, never silently.\n\nThe `name` option must match the `name` field in the project's `package.json` (for the CDK types). For the Java types it becomes the root pom's `<name>` and the generated `package.json` name.\n\n> **Never write projen's generated-file marker text literally in `.projenrc.ts`.** projen deletes, as an orphaned generated file, any file containing the marker line (`~~ Generated by projen. To modify, ...`), and that includes `.projenrc.ts` itself. When you generate a file yourself (for example with `TextFile`), insert the marker through the file's `marker` property: ``file.addLine(`# ${file.marker}`)``.\n\n## Starting from an existing `.projenrc.ts`\n\nA directory that has a `.projenrc.ts` but no `.projen/` directory has never\nbeen synthesized. This happens when you copy the rc from another repo, or paste\none of the [examples](#examples) into a fresh repo. In that state `npx projen`\nfails with:\n\n```\n👾 Unable to find projen project. Use \"projen new\" to create a new project.\n```\n\n`npx projen` doesn't run `.projenrc.ts` itself. It runs the `default` task\nlisted in `.projen/tasks.json`, and that file is written by the first synth.\nDo the first synth one of these two ways. After that, `npx projen` works as\nusual.\n\n**Option 1 (recommended): run `projen new` over the existing rc.** `projen\nnew` leaves an existing `.projenrc.ts` untouched and writes the rest of the\nscaffold, `.projen/` included. Pass the type and its required flags as you\nwould for a new repo. Their values don't have to match your rc, because the\n`npx projen` that follows re-synthesizes everything from the rc:\n\n```bash\nnpx projen new --from @xpertss/projen-types git_hub_action --name pull-request --sonar-host-url https://sonarcloud.io --no-git\nnpx projen\n```\n\nThe first command's output reflects only the flags. For example, a\n`GitHubActionProject`'s `test-dogfood.yml` is still the failing placeholder.\nThe second command applies everything in the rc (`dogfood`, `environments`,\nmodules, and so on).\n\n**Option 2 (Java types and `GitHubActionProject`): run the rc directly,\nonce.** The CDK types run their rc differently, so for those use option 1.\nInstall what the rc imports, give\nts-node a placeholder `tsconfig.projen.json` (the synth overwrites it with the\ngenerated one), and run the same command the generated `default` task runs:\n\n```bash\nnpm i -D projen @xpertss/projen-types constructs\necho '{}' > tsconfig.projen.json\nnpx -y -p ts-node@10.9.2 -p typescript@6.0.3 ts-node --project tsconfig.projen.json .projenrc.ts\nnpx projen\n```\n\nThe `echo` line works in bash and PowerShell. Don't drop `--project`: without\na tsconfig, ts-node fails on Node 22+ with `Unknown file extension \".ts\"`.\n\nEither way, commit `.projen/` along with the rest of the scaffold. A fresh\nclone then has its `tasks.json`, and `npx projen` works there straight away.\n\n## Updating an existing project when this package changes\n\nYour generated scaffold reflects the *installed* version of `@xpertss/projen-types` - `npx projen` reads your project type from the package in `node_modules`, not from this repository. So when this package ships a change (a bug fix, a new generated file, or altered workflow behavior), an existing project picks it up by bumping the dependency and re-synthesizing. A stale `node_modules` silently regenerates with the old behavior, so the bump is the load-bearing step.\n\nUsing the [`auto-commit` action](#githubactionproject) as a running example, say a new release adds the `# Purpose:` workflow header and SonarCloud wording. In the `auto-commit` repo:\n\n```bash\nnpm view @xpertss/projen-types version # what's the newest release?\nnpm i -D @xpertss/projen-types@latest # bump the installed project type\nnpx projen # re-synthesize every generated file\ngit diff # review before committing\n```\n\nThe diff here is the workflows gaining a `# Purpose:` comment and `sonar.yml` reflecting the SonarCloud wording. Commit the result with a normal `chore:` or `fix:` message - you never hand-edit the generated files themselves.\n\n## Examples\n\nEach example shows the `projen new` command first, then a fuller\n`.projenrc.ts`. Run the command first, then replace the generated\n`.projenrc.ts` with the example (or merge it in) and run `npx projen`. If you\npasted the example into a fresh repo first instead, see\n[Starting from an existing `.projenrc.ts`](#starting-from-an-existing-projenrcts).\n\n### CdkInfraProject\n\nPure infrastructure stacks with optional ECR/ECS and edge-networking constructs.\n\nScaffold it with the `cdk_infra` type:\n\n```bash\nnpx projen new --from @xpertss/projen-types cdk_infra --name media-edge-infra\n```\n\n```typescript\n// .projenrc.ts\nimport { CdkInfraProject } from '@xpertss/projen-types';\n\nconst project = new CdkInfraProject({\n name: 'media-edge-infra',\n environments: [\n 'dev',\n { name: 'stage', accountId: '111111111111', region: 'eu-central-1' },\n { name: 'prod', accountId: '222222222222', region: 'eu-central-1', requiresApproval: true },\n ],\n edgeResources: ['cloudfront', 'route53', 'sqs'],\n ecrEcs: { enabled: true, externalImageSource: true },\n});\n\nproject.synth();\n```\n\nYou get:\n\n- `cdk.json`, `cdk synth` / `cdk diff` / `cdk deploy` tasks, and the standard `AwsCdkTypeScriptApp` layout (CDK 2.189.1 by default).\n- `.github/workflows/build.yml` - PR build that hard-fails on projen drift.\n- `.github/workflows/deploy.yml` - manual dispatch with one `deploy-<env>` job per environment (runs `cdk deploy --all` with `--context environment=<env>`); `requiresApproval` environments get a GitHub Environment approval gate.\n- `src/constructs/edge-networking.ts` - helper constructs only for the requested `edgeResources` (`cloudfront`, `route53`, `apigateway`, `cognito`, `sqs`).\n- `src/constructs/ecr-ecs.ts` when `ecrEcs.enabled` - ECR repo + Fargate service; `externalImageSource: true` (default) means the service pulls an image built outside this repo.\n\n### CdkAppProject\n\n`CdkInfraProject` plus application source, a database construct, and an app-level build workflow.\n\nScaffold it with the `cdk_app` type:\n\n```bash\nnpx projen new --from @xpertss/projen-types cdk_app --name video-api\n```\n\n```typescript\n// .projenrc.ts\nimport { CdkAppProject } from '@xpertss/projen-types';\n\nconst project = new CdkAppProject({\n name: 'video-api',\n environments: ['dev', 'prod'],\n edgeResources: ['apigateway'],\n database: { engine: 'postgres', migrationTool: 'flyway' },\n appEntryPoint: 'src/app.ts',\n});\n\nproject.synth();\n```\n\nEverything from `CdkInfraProject`, plus:\n\n- `src/app.ts` - application entrypoint stub (`export function handler()`).\n- `src/handlers/example.ts` - API Gateway proxy handler stub, with `@types/aws-lambda` added as a dev dependency.\n- `src/constructs/database.ts` - a `Database` construct stub for the chosen engine (`postgres`/`mysql` -> RDS, `dynamodb` -> DynamoDB). `migrationTool` (no default, intentionally) is added as a dev dependency and referenced in the stub - wiring it up is left to you.\n- `.github/workflows/app-build.yml` - runs the project's test task on every PR, then checks for projen drift.\n\n### JavaMavenProject\n\nA plain Maven build with no framework and no publish target. It's the base of every other Java type, and it's usable on its own. It's single-module until you add a module, then it's a multi-module reactor.\n\nScaffold it with the `java_maven` type:\n\n```bash\nnpx projen new --from @xpertss/projen-types java_maven --name legacy-tools --group-id org.xpertss --artifact-id legacy-tools --java-version 1.8\n```\n\n**Single module:**\n\n```typescript\n// .projenrc.ts\nimport { JavaMavenProject } from '@xpertss/projen-types';\n\nconst project = new JavaMavenProject({\n name: 'legacy-tools',\n groupId: 'org.xpertss',\n artifactId: 'legacy-tools',\n javaVersion: '1.8',\n});\n\nproject.addDependency('commons-io/commons-io@2.20.0'); // exact version\nproject.addTestDependency('org.assertj/assertj-core@3.27.3');\n\nproject.synth();\n```\n\n**Multi-module:** each `addModule()` call adds a module directory with its own generated `pom.xml`. The root pom becomes the `pom`-packaged reactor parent. Everything stays one projen project, with one `.projen/`, one `.gitignore` and one `tasks.json`.\n\n```typescript\n// .projenrc.ts\nimport { JavaMavenProject } from '@xpertss/projen-types';\n\nconst project = new JavaMavenProject({\n name: 'toolkit',\n groupId: 'org.xpertss.toolkit',\n artifactId: 'toolkit-parent',\n version: '0.1.0-SNAPSHOT',\n javaVersion: '17',\n});\n\nconst model = project.addModule({ dir: 'toolkit-model', artifactId: 'toolkit-model', description: 'Domain model' });\nconst stub = project.addModule({ dir: 'tools/stub-model', artifactId: 'toolkit-stub-model' }); // nested dirs are fine\nconst cli = project.addModule({ dir: 'toolkit-cli', artifactId: 'toolkit-cli' });\n\nstub.addModuleDependency(model); // sibling module: versionless\ncli.addModuleDependency(model);\ncli.addDependency('info.picocli/picocli@4.7.7'); // pinned: the version moves to the parent\ncli.addDependency('org.yaml/snakeyaml'); // versionless: managed below\nproject.addManagedDependency('org.yaml/snakeyaml@2.4');\nproject.addBom('org.testcontainers/testcontainers-bom@1.21.3');\n\nproject.synth();\n```\n\nWhat the reactor looks like:\n\n- **Root `pom.xml`:**\n - `<packaging>pom</packaging>`, with `<modules>` in `addModule` order.\n - `<dependencyManagement>` holds the BOM imports first (`type=pom`, `scope=import`), then every module at `${project.version}`, then every pinned version.\n - `<pluginManagement>` holds the plugin versions, and versionless `<plugins>` are inherited by every module: compiler, surefire, failsafe, jar, and enforcer.\n - `junit-jupiter` is a test dependency every module inherits. Its version comes from `junit-bom`, or from the Spring Boot BOM in the Spring Boot types.\n- **Module `pom.xml`:** `<parent>` with the right `relativePath` (`tools/stub-model` → `../../pom.xml`), then `artifactId`, `name`, `description`, and versionless `<dependencies>`. Nothing else, unless the module adds plugins.\n- A version given to `module.addDependency`/`addTestDependency`/`addPlugin` is moved into the parent's `<dependencyManagement>`/`<pluginManagement>`, so every module that uses an artifact gets the same version. Pinning one artifact at two different versions fails the synth.\n- The synth fails with a clear message on:\n - two modules with the same `artifactId`\n - two modules with the same `dir`, or one `dir` nested inside another\n - a `dir` outside the repo\n - a module depending on itself\n - `packaging` set to anything but `pom` on a project with modules\n\nYou get:\n\n- the root `pom.xml` (and one per module), written by this package:\n - **exact versions only**: a range such as `^1`, `~1.2` or `[1,2)` fails the synth\n - the compiler level, enforcer rule and JUnit line all follow `javaVersion`\n- one Maven run per build. `npx projen build` synthesizes, then runs `mvn -B verify`: compile, unit tests (surefire), package, and `*IT` integration tests (failsafe). There is no `mvn deploy` and no `dist/`.\n- `.github/workflows/build.yml`: a PR build that installs the pinned Node toolchain (`npm ci`) and the project's JDK (`actions/setup-java`, Maven cache), then runs `npx projen build`, plus a SonarQube step when `sonarProjectKey` is set. See [Adding CI jobs](#adding-ci-jobs-to-a-java-project).\n- `.github/workflows/upgrade.yml`: a nightly (03:00 UTC) **report** of available dependency and plugin updates in the job summary. It changes nothing, because every version comes from `.projenrc.ts`; apply an update there. Turn it off with `upgradeWorkflow: false`. The same report runs locally with `npx projen upgrade`.\n- the projen drift check, `LICENSE` (MIT by default), `.editorconfig`, and a generated `package.json` that pins the projen toolchain exactly.\n- no sample code, unless `sample: true` on a single-module project.\n\n### JavaLibraryProject\n\nA reusable Java library published to Maven Central.\n\nScaffold it with the `java_library` type:\n\n```bash\nnpx projen new --from @xpertss/projen-types java_library --name common-utils --group-id org.xpertss --artifact-id common-utils\n```\n\n```typescript\n// .projenrc.ts\nimport { JavaLibraryProject } from '@xpertss/projen-types';\n\nconst project = new JavaLibraryProject({\n name: 'common-utils',\n groupId: 'org.xpertss',\n artifactId: 'common-utils',\n version: '1.0.0',\n sonarProjectKey: 'org.xpertss:common-utils',\n mavenCentralOidc: true,\n});\n\nproject.synth();\n```\n\nYou get everything from [`JavaMavenProject`](#javamavenproject), so modules work here too, plus:\n\n- `maven-source-plugin` and `maven-javadoc-plugin`, attaching the `-sources`/`-javadoc` jars Maven Central requires. In a multi-module library every module attaches them.\n- `.github/workflows/publish-maven-central.yml` - manual dispatch running `mvn -B deploy -P release`. With `mavenCentralOidc: true` it uses Maven Central's OIDC trusted publishing (no GPG secrets needed); otherwise it expects the secrets `MAVEN_GPG_PRIVATE_KEY`, `MAVEN_GPG_PASSPHRASE`, `MAVEN_CENTRAL_USERNAME`, `MAVEN_CENTRAL_PASSWORD`.\n- `.github/workflows/codeindex.yml` - on push to `main`, generates a Java source index under `.cai/` covering every module (disable with `publishCodeIndex: false`).\n\n### JavaSpringBootProject\n\nSpring Boot on Maven, single- or multi-module, **without** Docker, Flyway or a deploy hook. It's [`JavaMavenProject`](#javamavenproject) plus Spring Boot dependency management. Use it for a Spring Boot repo that ships something other than this package's container image, such as a multi-module control plane, or for a Boot app you deploy your own way.\n\nScaffold it with the `java_spring_boot` type:\n\n```bash\nnpx projen new --from @xpertss/projen-types java_spring_boot --name obeya --group-id org.xpertss.obeya --artifact-id obeya-parent\n```\n\n```typescript\n// .projenrc.ts\nimport { JavaSpringBootProject } from '@xpertss/projen-types';\n\nconst project = new JavaSpringBootProject({\n name: 'obeya',\n groupId: 'org.xpertss.obeya',\n artifactId: 'obeya-parent',\n version: '0.1.0-SNAPSHOT',\n javaVersion: '21',\n copyrightOwner: 'Xpert Software',\n});\n\n// plain jar modules: shared libraries, clients, tools\nconst model = project.addModule({ dir: 'obeya-model', artifactId: 'obeya-model' });\nconst client = project.addModule({ dir: 'obeya-api-client', artifactId: 'obeya-api-client' });\nclient.addModuleDependency(model);\n\n// a Spring Boot application module, repackaged into an executable jar\nconst server = project.addSpringBootModule({ dir: 'obeya-server', artifactId: 'obeya-server' });\nserver.addModuleDependency(model);\nserver.addDependency('org.springframework.boot/spring-boot-starter-web'); // versionless: Boot's BOM manages it\nserver.addTestDependency('org.springframework.boot/spring-boot-starter-test');\n\nproject.addBom('org.testcontainers/testcontainers-bom@1.21.3');\n\nproject.synth();\n```\n\nYou get everything from [`JavaMavenProject`](#javamavenproject), plus:\n\n- `spring-boot-dependencies` imported as the **first** BOM, so starters and every library Boot manages (JUnit included) are added without a version. `springBootVersion` sets it; the default is the newest release of the line that supports `javaVersion` (see [Java version support](#java-version-support)).\n- `spring-boot-maven-plugin`, versioned in `<pluginManagement>`:\n - In a **single-module** project the root jar is repackaged into an executable jar.\n - In a **multi-module** project only modules added with `addSpringBootModule()` are repackaged; `addModule()` modules stay plain jars.\n - A repackaged module needs its `@SpringBootApplication` class before `mvn verify` passes, because `repackage` fails with `Unable to find main class` until one exists. Add a module as a plain `addModule()` while it's still empty.\n- failsafe configured to run integration tests against the compiled classes rather than the repackaged jar (as `spring-boot-starter-parent` does).\n- no starters, no Docker, no Flyway, no CDK. Add the starters you need with `addDependency`.\n\n### JavaServiceProject\n\nA Spring Boot service that publishes a Docker image and can trigger deploys in a companion CDK repo. It is [`JavaSpringBootProject`](#javaspringbootproject) plus Docker, Flyway and a deploy hook. It is **single-module**, because the Docker build, the migrations and the deploy hook all assume one deployable at the repo root; `addModule()` fails and points you to `JavaSpringBootProject`.\n\nScaffold it with the `java_service` type:\n\n```bash\nnpx projen new --from @xpertss/projen-types java_service --name stream-processor --group-id org.xpertss --artifact-id stream-processor\n```\n\n```typescript\n// .projenrc.ts\nimport { JavaServiceProject } from '@xpertss/projen-types';\n\nconst project = new JavaServiceProject({\n name: 'stream-processor',\n groupId: 'org.xpertss',\n artifactId: 'stream-processor',\n dockerRegistry: 'docker.io/xpertss',\n cdkDeployTargetRepo: 'xpertss/stream-infra',\n environments: ['dev', { name: 'prod', requiresApproval: true }],\n});\n\nproject.synth();\n```\n\nYou get everything from [`JavaSpringBootProject`](#javaspringbootproject) (and so from `JavaMavenProject`), plus:\n\n- `spring-boot-starter-web` added to the pom (versionless; Boot's BOM manages it).\n- `.github/workflows/publish-docker.yml` - manual dispatch: `mvn -B package && docker build -t <registry>/<name>:<sha>`, logged in with the `DOCKER_USERNAME` / `DOCKER_PASSWORD` secrets. `dockerRegistry` defaults to `docker.io`.\n- Flyway wiring when `useFlyway` (default `true`): `flyway-core` (versionless, so it matches what Boot's Flyway auto-configuration expects), `flyway-maven-plugin` pinned for the Java line, and `src/main/resources/db/migration/V1__init.sql`.\n- `.github/workflows/deploy-cdk.yml` (the `CdkDeployHook`, generated by default) - manual dispatch with an environment selector; each job sends a `workflow_dispatch` to `deploy.yml` in the companion `cdkDeployTargetRepo` (a `CdkInfraProject`/`CdkAppProject` repo). With no `cdkDeployTargetRepo` set, the workflow is still generated but each job's only step fails with instructions - it is dispatch-only, so that lands on whoever tries to deploy rather than on every PR. Turn the workflow off entirely with `cdkDeployHook: false`. `environments` defaults to `['prod']`.\n\n### JavaAppProject\n\nA GUI/TUI/CLI Java application published to GitHub Packages only - no Maven Central, no Docker, no CDK deploy hook.\n\nScaffold it with the `java_app` type:\n\n```bash\nnpx projen new --from @xpertss/projen-types java_app --name studio-cli --group-id org.xpertss --artifact-id studio-cli\n```\n\n```typescript\n// .projenrc.ts\nimport { JavaAppProject } from '@xpertss/projen-types';\n\nconst project = new JavaAppProject({\n name: 'studio-cli',\n groupId: 'org.xpertss',\n artifactId: 'studio-cli',\n ghPackagesRegistry: 'https://maven.pkg.github.com/xpertss/studio-cli',\n});\n\nproject.synth();\n```\n\nYou get everything from [`JavaMavenProject`](#javamavenproject) (modules included), plus `.github/workflows/publish-ghpackages.yml` - manual dispatch running `mvn -B deploy -DaltDeploymentRepository=github::<registry>`, authenticated with `GITHUB_TOKEN`. `ghPackagesRegistry` defaults to `https://maven.pkg.github.com/<repo>` derived from the repository URL.\n\n### GitHubActionProject\n\nA reusable GitHub Action or Workflow. This example scaffolds an action that stages a folder and, only if it changed, commits and pushes it.\n\nScaffold it with the `git_hub_action` type:\n\n```bash\nnpx projen new --from @xpertss/projen-types git_hub_action --name auto-commit --sonar-host-url https://sonarcloud.io\n```\n\n```typescript\n// .projenrc.ts\nimport { GitHubActionProject } from '@xpertss/projen-types';\n\nconst project = new GitHubActionProject({\n name: 'auto-commit',\n description: 'Stage a folder and, only if it changed, commit and push it',\n sonarHostUrl: 'https://sonarcloud.io', // required, no default - your SonarCloud URL\n dogfood: {\n // Two scenario steps: the \"changed\" path and the \"no-op\" path are both\n // load-bearing behavior for this action (a double-commit or a\n // push-when-empty bug is the failure mode a hand-rolled inline-shell\n // alternative is most likely to introduce).\n scenario: [\n {\n name: 'Changed path',\n fixtureSteps: [\n 'echo \"$(date -u +%Y%m%dT%H%M%SZ)\" >> test/fixtures/dogfood-state.txt',\n ],\n inputs: {\n commit_message: 'test: dogfood',\n branches: 'test/dogfood',\n },\n assertions: [\n // step id defaults to a slug of `name` - here \"changed-path-0\"\n '[ \"${{ steps.changed-path-0.outputs.committed }}\" = \"true\" ]',\n ],\n },\n {\n // No fixture change this time - nothing new to commit.\n name: 'No-op path',\n id: 'no-op-check', // pin an explicit id instead of relying on the default slug\n inputs: {\n commit_message: 'test: dogfood',\n branches: 'test/dogfood',\n },\n assertions: [\n '[ \"${{ steps.no-op-check.outputs.committed }}\" = \"false\" ]',\n ],\n },\n ],\n cleanup: [\n 'git push origin --delete test/dogfood || true',\n ],\n },\n});\n\nproject.synth();\n```\n\nYou get:\n\n- `action.yml` and `auto-commit.sh` are hand-written - this type only lints their content via `build.yml`'s shellcheck/yamllint/actionlint checks.\n- `.github/workflows/build.yml` - lint gate: `apt`-installed shellcheck/yamllint plus a pinned, SHA-256-verified `actionlint` release binary. Gates `main` alongside `sonar.yml`.\n- `.github/workflows/test-dogfood.yml` - runs the `dogfood.scenario` steps above against this repo's own `action.yml` (via `uses: ./`), then the shared `cleanup`, on `workflow_dispatch`, every `pull_request`, and nightly. Omit `dogfood` and the workflow still exists, with one step that fails on every PR until you declare a scenario - AD-001 allows a dogfood to be missing loudly, never silently. A *partial* `dogfood` (a scenario with no cleanup) is a synth error.\n- `.github/workflows/sonar.yml` - SonarCloud scan via the Scanner CLI (the quality gate blocks the PR), scanning `action.yml`/`.github/workflows/**`/`**/*.sh` explicitly.\n- `.github/workflows/release.yml` - `feat:`/`fix:` commits on `main` bump the version, tag `vX.Y.Z`, and create a GitHub Release.\n- `.github/workflows/projen-drift-check.yml` and `workflow-change-notice.yml` - drift detection and a change notice, always included.\n- `package.json` (**private**, version source only), `.yamllint`, `LICENSE` (MIT by default), and a `README.md` template - all regenerated by `npx projen`.\n\nNeeds the same two secrets as everything else in this package: `PROJEN_GITHUB_TOKEN` (used for automated PR comments) and `SONAR_TOKEN` (the Sonar scan). Onboard a brand-new action repo by running the `projen new` command above first (see [Getting started](#getting-started)), then replace the generated `.projenrc.ts` with the one above and run `npx projen`, then hand-write `action.yml`/`auto-commit.sh`/`test/fixtures/`. If you wrote the `.projenrc.ts` before running `projen new`, see [Starting from an existing `.projenrc.ts`](#starting-from-an-existing-projenrcts).\n\n## Java version support\n\n`javaVersion` picks the Java line a Java project targets. It defaults to `21`; the supported lines are `1.8` (alias `8`), `17`, `21` and `25`, and any other value fails the synth with that list. **Nothing in the generated build is hard-coded to one Java line.** Everything that depends on it comes from one table in this package, grouped by line:\n\n| | `1.8` | `17` | `21` | `25` |\n| --- | --- | --- | --- | --- |\n| Compiler level | `maven.compiler.source`/`target` = `1.8` (javac 8 has no `--release`) | `maven.compiler.release` = `17` | `release` = `21` | `release` = `25` |\n| Enforcer `requireJavaVersion` | `[1.8,)` | `[17,)` | `[21,)` | `[25,)` |\n| JUnit (`junit-bom`) | 5.14.4 (JUnit 6 needs Java 17) | 6.1.3 | 6.1.3 | 6.1.3 |\n| Default Spring Boot (`JavaSpringBootProject`) | 2.7.18 (Boot 3+ needs Java 17; a 3.x+ `springBootVersion` fails the synth) | 4.1.1 | 4.1.1 | 4.1.1 |\n| `flyway-maven-plugin` (`JavaServiceProject`) | 9.22.3 | 13.9.0 | 13.9.0 | 13.9.0 |\n| CI JDK (`actions/setup-java` `java-version`) | `8` | `17` | `21` | `25` |\n\nMaven plugins (the same on every line, because all of them run on JDK 8): `maven-compiler-plugin` 3.16.0, `maven-surefire-plugin`/`maven-failsafe-plugin` 3.6.0, `maven-jar-plugin` 3.5.1, `maven-enforcer-plugin` 3.6.3, `maven-source-plugin` 3.4.0 and `maven-javadoc-plugin` 3.12.0 (library only), `versions-maven-plugin` 2.22.0 (the `upgrade` report).\n\n**The enforcer.** `maven-enforcer-plugin` fails the build at the start when the JDK or Maven running it is older than the project needs. Without it you get confusing compiler errors later, or a jar built for the wrong runtime. Its Java rule always follows `javaVersion`. The Maven rule defaults to `[3.9,)`; change it with `minMavenVersion`, or drop the plugin entirely with `enforcer: false`. CI installs the matching JDK through `setup-java`, so the rule only fires on a developer machine with the wrong JDK. A Java 1.8 project builds on any newer JDK (the rule is a minimum), but the JDK 8 runtime API is guaranteed only when you build on JDK 8.\n\n**Overriding a version.** `pluginVersions` replaces any default in the table, keyed by `groupId/artifactId`, with an exact version:\n\n```typescript\nnew JavaMavenProject({\n name: 'svc',\n groupId: 'org.xpertss',\n artifactId: 'svc',\n pluginVersions: {\n 'org.apache.maven.plugins/maven-surefire-plugin': '3.5.6',\n 'org.junit/junit-bom': '5.14.4',\n },\n});\n```\n\n`project.pinnedVersion('org.apache.maven.plugins/maven-surefire-plugin')` returns the version in effect, for reuse in your own plugin config.\n\n**New Java lines** are one new row in this package's version table and a release. Until a line is listed, `javaVersion` rejects it rather than guessing.\n\n## Adding CI jobs to a Java project\n\n`build.yml` is exposed as `project.buildVerifyWorkflow`, so jobs are added through projen's `addJob` rather than `addOverride`. `project.ciSetupSteps` holds the steps every Maven job here needs: pinned Node, `npm ci`, and the project's JDK with a Maven cache. Every job in `build.yml` can then be made a required check on `main`.\n\n```typescript\nimport { github } from 'projen';\n\nproject.buildVerifyWorkflow.addJob('integration', {\n name: 'Container integration tests',\n runsOn: ['self-hosted', 'linux', 'fedora', 'podman'],\n permissions: { contents: github.workflows.JobPermission.READ },\n steps: [\n { name: 'Checkout', uses: 'actions/checkout@v7' },\n ...project.ciSetupSteps,\n { name: 'Integration tests', run: 'mvn -B verify -Pcontainer-its' },\n ],\n});\n```\n\n## Common options\n\nCDK project types (`CdkInfraProjectOptions` / `CdkAppProjectOptions`):\n\n| Option | Default | Description |\n| --- | --- | --- |\n| `name` | - (required) | Project name; must match `package.json` |\n| `cdkVersion` | `2.189.1` | AWS CDK version |\n| `gheTokenSecret` | `PROJEN_GITHUB_TOKEN` | GitHub secret holding projen's PAT |\n| `environments` | - (no `deploy` workflow) | Deploy targets for the `deploy` workflow; strings or `EnvironmentOptions` |\n| `ecrEcs` | - | `EcrEcsOptions` - `enabled`, `externalImageSource` (default `true`) |\n| `edgeResources` | - | Subset of `cloudfront`, `route53`, `apigateway`, `cognito`, `sqs` |\n| `database` | - (app only) | `DatabaseOptions` - `engine` (`postgres`/`mysql`/`dynamodb`, default `postgres`), `migrationTool` |\n| `appEntryPoint` | `src/app.ts` (app only) | Path of the generated application entrypoint |\n\nJava project types (`JavaMavenProjectOptions` and everything built on it - `JavaLibraryProjectOptions`, `JavaAppProjectOptions`, `JavaSpringBootProjectOptions`, `JavaServiceProjectOptions`):\n\n| Option | Default | Description |\n| --- | --- | --- |\n| `name` | - (required) | Project name |\n| `groupId` | - (required) | Maven group id |\n| `artifactId` | - (required) | Maven artifact id (of the reactor parent, in a multi-module project) |\n| `version` | `0.1.0` | Maven version; must be exact |\n| `description` / `url` | - | Written to the root pom |\n| `javaVersion` | `21` | Java line: `1.8`, `17`, `21`, `25` - see [Java version support](#java-version-support) |\n| `javaDistribution` | `temurin` | `actions/setup-java` distribution for CI |\n| `packaging` | `jar` | Root packaging while the project has no modules; a project with modules is always `pom` |\n| `sample` | `false` | Starter `Main` + test under the `groupId` package (single-module only) |\n| `enforcer` | `true` | `maven-enforcer-plugin` with Maven and Java version rules |\n| `minMavenVersion` | `3.9` | The enforcer's `requireMavenVersion` minimum |\n| `pluginVersions` | - | Exact-version overrides for this package's defaults, keyed by `groupId/artifactId` |\n| `licensed` | `true` | Write a `LICENSE` |\n| `license` | `MIT` | SPDX identifier for the `LICENSE` |\n| `copyrightOwner` / `copyrightPeriod` | `xpertss` / current year | Named in the `LICENSE` |\n| `editorconfig` | `true` | Write a projen-managed `.editorconfig` (also on the CDK and action types) |\n| `upgradeWorkflow` | `true` | Generate the nightly update report (`upgrade.yml`) |\n| `sonarProjectKey` | - | SonarQube project key; the sonar step is skipped when unset |\n| `gheTokenSecret` | `PROJEN_GITHUB_TOKEN` | GitHub secret holding projen's PAT |\n| `springBootVersion` | newest Boot for `javaVersion` (Spring Boot types only) | Exact Spring Boot version: the BOM and `spring-boot-maven-plugin` |\n| `cdkDeployTargetRepo` | - (service only; `deploy-cdk.yml` fails until set) | Companion CDK repo (`owner/repo`) whose `deploy.yml` the deploy hook dispatches |\n| `cdkDeployHook` | `true` (service only) | Whether to generate `deploy-cdk.yml` at all |\n| `dockerRegistry` | `docker.io` (service only) | Registry the Docker image is pushed to |\n| `useFlyway` | `true` (service only) | Flyway plugin/dependency + `V1__init.sql` |\n\nWith the default MIT license, the generated `LICENSE` looks like:\n\n```\nMIT License\n\nCopyright (c) 2024-2026 Xpert Software\n\nPermission is hereby granted, ...\n```\n\nThe header line (`MIT License`) is specific to the license type — an `Apache-2.0` project would instead show `Apache-2.0`.\n\nOverride the license type and copyright fields:\n\n```typescript\nnew JavaMavenProject({\n name: 'my-lib',\n groupId: 'org.xpertss',\n artifactId: 'my-lib',\n license: 'Apache-2.0', // any SPDX id projen ships a template for\n copyrightOwner: 'Xpert Software',\n copyrightPeriod: '2024-2026',\n});\n```\n\n`EnvironmentOptions` for deploy targets:\n\n```text\ninterface EnvironmentOptions {\n readonly name: string; // e.g. \"dev\", \"stage\", \"prod\"\n readonly accountId?: string; // AWS account id (CDK deploys)\n readonly region?: string; // AWS region (CDK deploys)\n readonly requiresApproval?: boolean; // GitHub Environment approval gate (default false)\n}\n```\n\nPlain strings (`'dev'`) are shorthand for `{ name: 'dev' }`.\n\n`GitHubActionProjectOptions`:\n\n| Option | Default | Description |\n| --- | --- | --- |\n| `name` | - (required) | Project name |\n| `description` | - | One-line description; used in the default README template and recorded in the private `package.json` |\n| `sonarHostUrl` | - (required) | URL of your SonarCloud instance (e.g. `https://sonarcloud.io`); must be reachable from github.com-hosted runners |\n| `sonarTokenSecret` | `SONAR_TOKEN` | GitHub secret holding the Sonar token |\n| `sonarPullRequestGate` | `true` | Whether `sonar.yml` also runs on `pull_request` as a pass/fail gate |\n| `dogfood` | - (a `test-dogfood.yml` that fails until you declare one) | `ActionDogfoodOptions` - the scenario that exercises the action end-to-end via `uses: ./` |\n| `license` | `MIT` | SPDX identifier for the generated `LICENSE` |\n| `gheTokenSecret` | `PROJEN_GITHUB_TOKEN` | GitHub secret holding projen's PAT |\n\n`ActionDogfoodOptions`/`ActionDogfoodStep` - the dogfood scenario (`test-dogfood.yml`):\n\n```text\ninterface ActionDogfoodOptions {\n readonly scenario: ActionDogfoodStep[]; // one or more, run in order - required\n readonly cleanup: string[]; // shared, run once at the end with `if: always()` - required\n}\n\ninterface ActionDogfoodStep {\n readonly name: string; // labels this step-group's generated workflow steps\n readonly id?: string; // step id for the `uses: ./` call; default: a slug of `name`\n readonly fixtureSteps?: string[]; // shell, before the invocation (default: none)\n readonly inputs?: Record<string, string>; // `with:` for this invocation (default: none)\n readonly assertions: string[]; // shell, after the invocation - required, job fails unless all exit 0\n}\n```\n\nMost actions need exactly one `scenario` step. Actions with a re-run/no-op/idempotency behavior to verify (e.g. `auto-commit`'s no-op-on-no-change path, `create-pull-request`'s reuse-the-PR path) declare two - the second typically omits `fixtureSteps` so its invocation sees no new state, and its assertion checks the opposite outcome of the first. Reference an invocation's own outputs from a later assertion via `${{ steps.<id>.outputs.<name> }}`, using either the default slug or an explicit `id`.\n\n## Required GitHub secrets\n\n| Secret | Used by | Notes |\n| --- | --- | --- |\n| `PROJEN_GITHUB_TOKEN` | all types | PAT for projen's self-mutation/automation; override via `gheTokenSecret` |\n| `SONAR_TOKEN` | Java types with `sonarProjectKey`; `GitHubActionProject` | SonarQube scan step in `build.yml` / `sonar.yml`; override via `sonarTokenSecret` on `GitHubActionProject` |\n| `DOCKER_USERNAME` / `DOCKER_PASSWORD` | `JavaServiceProject` | Docker image push |\n| `MAVEN_GPG_PRIVATE_KEY`, `MAVEN_GPG_PASSPHRASE`, `MAVEN_CENTRAL_USERNAME`, `MAVEN_CENTRAL_PASSWORD` | `JavaLibraryProject` without `mavenCentralOidc` | Not needed with OIDC trusted publishing |\n\n## Customizing `.gitignore`\n\nEvery project type already ignores JetBrains IDE state (`/.idea/*`) and the `/spec/` directory with all of its subdirectories (local plans and specs, never committed). The Java types also ignore Maven `target/` output and Eclipse files. To ignore more, call the inherited `addGitIgnore()` method after constructing the project in your `.projenrc.ts`:\n\n```typescript\n// .projenrc.ts\nimport { CdkInfraProject } from '@xpertss/projen-types';\n\nconst project = new CdkInfraProject({\n name: 'my-infra',\n});\n\nproject.addGitIgnore('*.log');\nproject.addGitIgnore('.env.*');\nproject.addGitIgnore('/build/');\n\nproject.synth();\n```\n\nThe same works for every type - just swap the class (e.g. `JavaServiceProject`, `GitHubActionProject`). Each call takes one standard gitignore glob pattern. There is no `gitignore` constructor option: the project types' option interfaces do not expose one, so patterns go through `addGitIgnore()` instead.\n\nRe-run `npx projen` after editing - `.gitignore` is a generated file, so hand-editing it is caught by the drift check.\n\n## Troubleshooting\n\n**`Unable to find projen project. Use \"projen new\" to create a new project.`**\nIf the directory already has a `.projenrc.ts`, this error means the rc has\nnever been synthesized (there's no `.projen/tasks.json`), not that you need a\nnew project. See [Starting from an existing `.projenrc.ts`](#starting-from-an-existing-projenrcts).\n\n## Going further\n\nThe project types are composed from smaller components you can also attach to your own projects:\n\n| Component | Applies to | Purpose |\n| --- | --- | --- |\n| `AppRuntimeScaffold` | `NodeProject` | App source skeleton (`app.ts` + handlers) |\n| `DatabaseComponent` | `NodeProject` | Database construct stub + migration tool wiring |\n| `EcrEcsConstructs` | `Project` | ECR + Fargate ECS construct helper |\n| `EdgeNetworkingConstructs` | `Project` | Per-resource edge networking construct helpers |\n| `MavenPom` | `Project` | A `pom.xml` with modules, BOM imports, dependency/plugin management, and exact versions only |\n| `MavenModule` | `JavaMavenProject` | One module of a reactor (created by `addModule()`) |\n| `MavenUpgradeReport` | `GitHubProject` | Nightly report-only `upgrade.yml` |\n| `MavenCentralPublish` | `JavaMavenProject` | Manual-dispatch Maven Central publish workflow |\n| `DockerPublish` | `JavaMavenProject` | Manual-dispatch Docker build+push workflow |\n| `GitHubPackagesPublish` | `JavaMavenProject` | Manual-dispatch GitHub Packages publish workflow |\n| `FlywayMigration` | `JavaSpringBootProject` | Flyway plugin/dependency + migrations directory |\n| `CdkDeployHook` | `JavaMavenProject` | Manual-dispatch workflow that triggers `deploy.yml` in a companion CDK repo |\n| `CodeIndexWorkflow` | `JavaMavenProject` | Code index generation on push to `main` |\n| `ActionBuildWorkflow` | `GitHubProject` | The `lint` task (shellcheck/yamllint/pinned actionlint) + `build.yml` |\n| `ActionDogfoodWorkflow` | `GitHubProject` | `test-dogfood.yml` from an `ActionDogfoodOptions` scenario |\n| `ActionSonarWorkflow` | `GitHubProject` | `sonar.yml` (SonarCloud scan via the Scanner CLI) |\n\nExample - adding a Docker publish to a `JavaMavenProject` (`JavaServiceProject` is the packaged version of this, with Spring Boot):\n\n```typescript\nimport { DockerPublish, JavaMavenProject } from '@xpertss/projen-types';\n\nconst project = new JavaMavenProject({\n name: 'my-service',\n groupId: 'org.xpertss',\n artifactId: 'my-service',\n});\n\nnew DockerPublish(project, { dockerRegistry: 'ghcr.io' });\n\nproject.synth();\n```\n\nThe full API reference, including every option and property, is in [API.md](./API.md).\n"
110
110
  },
111
111
  "repository": {
112
112
  "type": "git",
@@ -2406,7 +2406,7 @@
2406
2406
  },
2407
2407
  "locationInModule": {
2408
2408
  "filename": "src/java/java-maven-base.ts",
2409
- "line": 84
2409
+ "line": 85
2410
2410
  },
2411
2411
  "parameters": [
2412
2412
  {
@@ -2420,7 +2420,7 @@
2420
2420
  "kind": "class",
2421
2421
  "locationInModule": {
2422
2422
  "filename": "src/java/java-maven-base.ts",
2423
- "line": 55
2423
+ "line": 56
2424
2424
  },
2425
2425
  "methods": [
2426
2426
  {
@@ -2430,7 +2430,7 @@
2430
2430
  },
2431
2431
  "locationInModule": {
2432
2432
  "filename": "src/java/java-maven-base.ts",
2433
- "line": 274
2433
+ "line": 275
2434
2434
  },
2435
2435
  "name": "addBom",
2436
2436
  "parameters": [
@@ -2453,7 +2453,7 @@
2453
2453
  },
2454
2454
  "locationInModule": {
2455
2455
  "filename": "src/java/java-maven-base.ts",
2456
- "line": 296
2456
+ "line": 297
2457
2457
  },
2458
2458
  "name": "addDependency",
2459
2459
  "parameters": [
@@ -2475,7 +2475,7 @@
2475
2475
  },
2476
2476
  "locationInModule": {
2477
2477
  "filename": "src/java/java-maven-base.ts",
2478
- "line": 284
2478
+ "line": 285
2479
2479
  },
2480
2480
  "name": "addManagedDependency",
2481
2481
  "parameters": [
@@ -2498,7 +2498,7 @@
2498
2498
  },
2499
2499
  "locationInModule": {
2500
2500
  "filename": "src/java/java-maven-base.ts",
2501
- "line": 243
2501
+ "line": 244
2502
2502
  },
2503
2503
  "name": "addModule",
2504
2504
  "parameters": [
@@ -2522,7 +2522,7 @@
2522
2522
  },
2523
2523
  "locationInModule": {
2524
2524
  "filename": "src/java/java-maven-base.ts",
2525
- "line": 306
2525
+ "line": 307
2526
2526
  },
2527
2527
  "name": "addPlugin",
2528
2528
  "parameters": [
@@ -2548,7 +2548,7 @@
2548
2548
  },
2549
2549
  "locationInModule": {
2550
2550
  "filename": "src/java/java-maven-base.ts",
2551
- "line": 301
2551
+ "line": 302
2552
2552
  },
2553
2553
  "name": "addTestDependency",
2554
2554
  "parameters": [
@@ -2567,7 +2567,7 @@
2567
2567
  },
2568
2568
  "locationInModule": {
2569
2569
  "filename": "src/java/java-maven-base.ts",
2570
- "line": 334
2570
+ "line": 335
2571
2571
  },
2572
2572
  "name": "maxSpringBootMajor",
2573
2573
  "protected": true,
@@ -2585,7 +2585,7 @@
2585
2585
  },
2586
2586
  "locationInModule": {
2587
2587
  "filename": "src/java/java-maven-base.ts",
2588
- "line": 322
2588
+ "line": 323
2589
2589
  },
2590
2590
  "name": "pinnedVersion",
2591
2591
  "parameters": [
@@ -2609,7 +2609,7 @@
2609
2609
  },
2610
2610
  "locationInModule": {
2611
2611
  "filename": "src/java/java-maven-base.ts",
2612
- "line": 310
2612
+ "line": 311
2613
2613
  },
2614
2614
  "name": "preSynthesize",
2615
2615
  "overrides": "projen.Project"
@@ -2625,7 +2625,7 @@
2625
2625
  "immutable": true,
2626
2626
  "locationInModule": {
2627
2627
  "filename": "src/java/java-maven-base.ts",
2628
- "line": 70
2628
+ "line": 71
2629
2629
  },
2630
2630
  "name": "buildVerifyWorkflow",
2631
2631
  "type": {
@@ -2640,7 +2640,7 @@
2640
2640
  "immutable": true,
2641
2641
  "locationInModule": {
2642
2642
  "filename": "src/java/java-maven-base.ts",
2643
- "line": 76
2643
+ "line": 77
2644
2644
  },
2645
2645
  "name": "ciSetupSteps",
2646
2646
  "type": {
@@ -2660,7 +2660,7 @@
2660
2660
  "immutable": true,
2661
2661
  "locationInModule": {
2662
2662
  "filename": "src/java/java-maven-base.ts",
2663
- "line": 60
2663
+ "line": 61
2664
2664
  },
2665
2665
  "name": "javaVersion",
2666
2666
  "type": {
@@ -2675,7 +2675,7 @@
2675
2675
  "immutable": true,
2676
2676
  "locationInModule": {
2677
2677
  "filename": "src/java/java-maven-base.ts",
2678
- "line": 232
2678
+ "line": 233
2679
2679
  },
2680
2680
  "name": "modules",
2681
2681
  "type": {
@@ -2695,7 +2695,7 @@
2695
2695
  "immutable": true,
2696
2696
  "locationInModule": {
2697
2697
  "filename": "src/java/java-maven-base.ts",
2698
- "line": 57
2698
+ "line": 58
2699
2699
  },
2700
2700
  "name": "pom",
2701
2701
  "type": {
@@ -2710,7 +2710,7 @@
2710
2710
  "immutable": true,
2711
2711
  "locationInModule": {
2712
2712
  "filename": "src/java/java-maven-base.ts",
2713
- "line": 63
2713
+ "line": 64
2714
2714
  },
2715
2715
  "name": "upgradeTask",
2716
2716
  "type": {
@@ -4483,6 +4483,6 @@
4483
4483
  "symbolId": "src/common/workflow-change-notice-workflow:WorkflowChangeNoticeWorkflowOptions"
4484
4484
  }
4485
4485
  },
4486
- "version": "0.0.17",
4487
- "fingerprint": "Ezxs+RHKOMYGbDs43hj50p8S4BfisJneuNo29Qaax7o="
4486
+ "version": "0.0.19",
4487
+ "fingerprint": "DUU5my6IaU2S2jf2l/2PqK+vKfee/Wto+QTx7P4Ykpc="
4488
4488
  }
package/README.md CHANGED
@@ -39,6 +39,14 @@ All project types:
39
39
 
40
40
  ## Getting started
41
41
 
42
+ How you start depends on what is already in the directory:
43
+
44
+ | Starting point | Do this |
45
+ | --- | --- |
46
+ | Empty directory | `projen new --from` (below) |
47
+ | Existing repo, no `.projenrc.ts` | `projen new --from ... --no-git` (below) |
48
+ | A `.projenrc.ts` but no `.projen/` directory - copied from another repo, or one of the [examples](#examples) pasted into a fresh repo | [Starting from an existing `.projenrc.ts`](#starting-from-an-existing-projenrcts) |
49
+
42
50
  Scaffold the repo with projen's own bootstrap, pointed at this package:
43
51
 
44
52
  ```bash
@@ -99,6 +107,57 @@ The `name` option must match the `name` field in the project's `package.json` (f
99
107
 
100
108
  > **Never write projen's generated-file marker text literally in `.projenrc.ts`.** projen deletes, as an orphaned generated file, any file containing the marker line (`~~ Generated by projen. To modify, ...`), and that includes `.projenrc.ts` itself. When you generate a file yourself (for example with `TextFile`), insert the marker through the file's `marker` property: ``file.addLine(`# ${file.marker}`)``.
101
109
 
110
+ ## Starting from an existing `.projenrc.ts`
111
+
112
+ A directory that has a `.projenrc.ts` but no `.projen/` directory has never
113
+ been synthesized. This happens when you copy the rc from another repo, or paste
114
+ one of the [examples](#examples) into a fresh repo. In that state `npx projen`
115
+ fails with:
116
+
117
+ ```
118
+ 👾 Unable to find projen project. Use "projen new" to create a new project.
119
+ ```
120
+
121
+ `npx projen` doesn't run `.projenrc.ts` itself. It runs the `default` task
122
+ listed in `.projen/tasks.json`, and that file is written by the first synth.
123
+ Do the first synth one of these two ways. After that, `npx projen` works as
124
+ usual.
125
+
126
+ **Option 1 (recommended): run `projen new` over the existing rc.** `projen
127
+ new` leaves an existing `.projenrc.ts` untouched and writes the rest of the
128
+ scaffold, `.projen/` included. Pass the type and its required flags as you
129
+ would for a new repo. Their values don't have to match your rc, because the
130
+ `npx projen` that follows re-synthesizes everything from the rc:
131
+
132
+ ```bash
133
+ npx projen new --from @xpertss/projen-types git_hub_action --name pull-request --sonar-host-url https://sonarcloud.io --no-git
134
+ npx projen
135
+ ```
136
+
137
+ The first command's output reflects only the flags. For example, a
138
+ `GitHubActionProject`'s `test-dogfood.yml` is still the failing placeholder.
139
+ The second command applies everything in the rc (`dogfood`, `environments`,
140
+ modules, and so on).
141
+
142
+ **Option 2 (Java types and `GitHubActionProject`): run the rc directly,
143
+ once.** The CDK types run their rc differently, so for those use option 1.
144
+ Install what the rc imports, give
145
+ ts-node a placeholder `tsconfig.projen.json` (the synth overwrites it with the
146
+ generated one), and run the same command the generated `default` task runs:
147
+
148
+ ```bash
149
+ npm i -D projen @xpertss/projen-types constructs
150
+ echo '{}' > tsconfig.projen.json
151
+ npx -y -p ts-node@10.9.2 -p typescript@6.0.3 ts-node --project tsconfig.projen.json .projenrc.ts
152
+ npx projen
153
+ ```
154
+
155
+ The `echo` line works in bash and PowerShell. Don't drop `--project`: without
156
+ a tsconfig, ts-node fails on Node 22+ with `Unknown file extension ".ts"`.
157
+
158
+ Either way, commit `.projen/` along with the rest of the scaffold. A fresh
159
+ clone then has its `tasks.json`, and `npx projen` works there straight away.
160
+
102
161
  ## Updating an existing project when this package changes
103
162
 
104
163
  Your generated scaffold reflects the *installed* version of `@xpertss/projen-types` - `npx projen` reads your project type from the package in `node_modules`, not from this repository. So when this package ships a change (a bug fix, a new generated file, or altered workflow behavior), an existing project picks it up by bumping the dependency and re-synthesizing. A stale `node_modules` silently regenerates with the old behavior, so the bump is the load-bearing step.
@@ -116,6 +175,12 @@ The diff here is the workflows gaining a `# Purpose:` comment and `sonar.yml` re
116
175
 
117
176
  ## Examples
118
177
 
178
+ Each example shows the `projen new` command first, then a fuller
179
+ `.projenrc.ts`. Run the command first, then replace the generated
180
+ `.projenrc.ts` with the example (or merge it in) and run `npx projen`. If you
181
+ pasted the example into a fresh repo first instead, see
182
+ [Starting from an existing `.projenrc.ts`](#starting-from-an-existing-projenrcts).
183
+
119
184
  ### CdkInfraProject
120
185
 
121
186
  Pure infrastructure stacks with optional ECR/ECS and edge-networking constructs.
@@ -139,7 +204,6 @@ const project = new CdkInfraProject({
139
204
  ],
140
205
  edgeResources: ['cloudfront', 'route53', 'sqs'],
141
206
  ecrEcs: { enabled: true, externalImageSource: true },
142
- slackWebhookSecret: 'SLACK_DEPLOY_WEBHOOK',
143
207
  });
144
208
 
145
209
  project.synth();
@@ -149,7 +213,7 @@ You get:
149
213
 
150
214
  - `cdk.json`, `cdk synth` / `cdk diff` / `cdk deploy` tasks, and the standard `AwsCdkTypeScriptApp` layout (CDK 2.189.1 by default).
151
215
  - `.github/workflows/build.yml` - PR build that hard-fails on projen drift.
152
- - `.github/workflows/deploy.yml` - manual dispatch with one `deploy-<env>` job per environment (runs `cdk deploy --all` with `--context environment=<env>`); `requiresApproval` environments get a GitHub Environment approval gate; a Slack notification step is added when `slackWebhookSecret` is set.
216
+ - `.github/workflows/deploy.yml` - manual dispatch with one `deploy-<env>` job per environment (runs `cdk deploy --all` with `--context environment=<env>`); `requiresApproval` environments get a GitHub Environment approval gate.
153
217
  - `src/constructs/edge-networking.ts` - helper constructs only for the requested `edgeResources` (`cloudfront`, `route53`, `apigateway`, `cognito`, `sqs`).
154
218
  - `src/constructs/ecr-ecs.ts` when `ecrEcs.enabled` - ECR repo + Fargate service; `externalImageSource: true` (default) means the service pulls an image built outside this repo.
155
219
 
@@ -479,7 +543,7 @@ You get:
479
543
  - `.github/workflows/projen-drift-check.yml` and `workflow-change-notice.yml` - drift detection and a change notice, always included.
480
544
  - `package.json` (**private**, version source only), `.yamllint`, `LICENSE` (MIT by default), and a `README.md` template - all regenerated by `npx projen`.
481
545
 
482
- Needs the same two secrets as everything else in this package: `PROJEN_GITHUB_TOKEN` (used for automated PR comments) and `SONAR_TOKEN` (the Sonar scan). Onboard a brand-new action repo following [Getting started](#getting-started), write the `.projenrc.ts` above, then hand-write `action.yml`/`auto-commit.sh`/`test/fixtures/`.
546
+ Needs the same two secrets as everything else in this package: `PROJEN_GITHUB_TOKEN` (used for automated PR comments) and `SONAR_TOKEN` (the Sonar scan). Onboard a brand-new action repo by running the `projen new` command above first (see [Getting started](#getting-started)), then replace the generated `.projenrc.ts` with the one above and run `npx projen`, then hand-write `action.yml`/`auto-commit.sh`/`test/fixtures/`. If you wrote the `.projenrc.ts` before running `projen new`, see [Starting from an existing `.projenrc.ts`](#starting-from-an-existing-projenrcts).
483
547
 
484
548
  ## Java version support
485
549
 
@@ -544,7 +608,6 @@ CDK project types (`CdkInfraProjectOptions` / `CdkAppProjectOptions`):
544
608
  | `name` | - (required) | Project name; must match `package.json` |
545
609
  | `cdkVersion` | `2.189.1` | AWS CDK version |
546
610
  | `gheTokenSecret` | `PROJEN_GITHUB_TOKEN` | GitHub secret holding projen's PAT |
547
- | `slackWebhookSecret` | - | GitHub secret with a Slack webhook URL for deploy notifications |
548
611
  | `environments` | - (no `deploy` workflow) | Deploy targets for the `deploy` workflow; strings or `EnvironmentOptions` |
549
612
  | `ecrEcs` | - | `EcrEcsOptions` - `enabled`, `externalImageSource` (default `true`) |
550
613
  | `edgeResources` | - | Subset of `cloudfront`, `route53`, `apigateway`, `cognito`, `sqs` |
@@ -580,6 +643,31 @@ Java project types (`JavaMavenProjectOptions` and everything built on it - `Java
580
643
  | `dockerRegistry` | `docker.io` (service only) | Registry the Docker image is pushed to |
581
644
  | `useFlyway` | `true` (service only) | Flyway plugin/dependency + `V1__init.sql` |
582
645
 
646
+ With the default MIT license, the generated `LICENSE` looks like:
647
+
648
+ ```
649
+ MIT License
650
+
651
+ Copyright (c) 2024-2026 Xpert Software
652
+
653
+ Permission is hereby granted, ...
654
+ ```
655
+
656
+ The header line (`MIT License`) is specific to the license type — an `Apache-2.0` project would instead show `Apache-2.0`.
657
+
658
+ Override the license type and copyright fields:
659
+
660
+ ```typescript
661
+ new JavaMavenProject({
662
+ name: 'my-lib',
663
+ groupId: 'org.xpertss',
664
+ artifactId: 'my-lib',
665
+ license: 'Apache-2.0', // any SPDX id projen ships a template for
666
+ copyrightOwner: 'Xpert Software',
667
+ copyrightPeriod: '2024-2026',
668
+ });
669
+ ```
670
+
583
671
  `EnvironmentOptions` for deploy targets:
584
672
 
585
673
  ```text
@@ -633,7 +721,6 @@ Most actions need exactly one `scenario` step. Actions with a re-run/no-op/idemp
633
721
  | `SONAR_TOKEN` | Java types with `sonarProjectKey`; `GitHubActionProject` | SonarQube scan step in `build.yml` / `sonar.yml`; override via `sonarTokenSecret` on `GitHubActionProject` |
634
722
  | `DOCKER_USERNAME` / `DOCKER_PASSWORD` | `JavaServiceProject` | Docker image push |
635
723
  | `MAVEN_GPG_PRIVATE_KEY`, `MAVEN_GPG_PASSPHRASE`, `MAVEN_CENTRAL_USERNAME`, `MAVEN_CENTRAL_PASSWORD` | `JavaLibraryProject` without `mavenCentralOidc` | Not needed with OIDC trusted publishing |
636
- | (your Slack webhook secret) | CDK types with `slackWebhookSecret` | Deploy notifications |
637
724
 
638
725
  ## Customizing `.gitignore`
639
726
 
@@ -658,6 +745,13 @@ The same works for every type - just swap the class (e.g. `JavaServiceProject`,
658
745
 
659
746
  Re-run `npx projen` after editing - `.gitignore` is a generated file, so hand-editing it is caught by the drift check.
660
747
 
748
+ ## Troubleshooting
749
+
750
+ **`Unable to find projen project. Use "projen new" to create a new project.`**
751
+ If the directory already has a `.projenrc.ts`, this error means the rc has
752
+ never been synthesized (there's no `.projen/tasks.json`), not that you need a
753
+ new project. See [Starting from an existing `.projenrc.ts`](#starting-from-an-existing-projenrcts).
754
+
661
755
  ## Going further
662
756
 
663
757
  The project types are composed from smaller components you can also attach to your own projects:
@@ -20,7 +20,7 @@ const ACTIONLINT_SHA256 = '8aca8db96f1b94770f1b0d72b6dddcb1ebb8123cb3712530b08cc
20
20
  * the release build job, so the lint commands exist in exactly one place.
21
21
  */
22
22
  class ActionBuildWorkflow extends projen_1.Component {
23
- static [JSII_RTTI_SYMBOL_1] = { fqn: "@xpertss/projen-types.ActionBuildWorkflow", version: "0.0.17" };
23
+ static [JSII_RTTI_SYMBOL_1] = { fqn: "@xpertss/projen-types.ActionBuildWorkflow", version: "0.0.19" };
24
24
  task;
25
25
  workflow;
26
26
  constructor(scope) {
@@ -41,7 +41,7 @@ const UNCONFIGURED_STEPS = [
41
41
  * error: if you wrote a scenario by hand, you can write its cleanup too.
42
42
  */
43
43
  class ActionDogfoodWorkflow extends projen_1.Component {
44
- static [JSII_RTTI_SYMBOL_1] = { fqn: "@xpertss/projen-types.ActionDogfoodWorkflow", version: "0.0.17" };
44
+ static [JSII_RTTI_SYMBOL_1] = { fqn: "@xpertss/projen-types.ActionDogfoodWorkflow", version: "0.0.19" };
45
45
  workflow;
46
46
  constructor(scope, options) {
47
47
  super(scope, 'ActionDogfoodWorkflow');
@@ -15,7 +15,7 @@ const SONAR_SCANNER_SHA256 = 'bb8f709f9cb73352f8d1260a3b3c506c0f41146754bc630762
15
15
  * inclusions may skip `action.yml` outside `.github/`.
16
16
  */
17
17
  class ActionSonarWorkflow extends projen_1.Component {
18
- static [JSII_RTTI_SYMBOL_1] = { fqn: "@xpertss/projen-types.ActionSonarWorkflow", version: "0.0.17" };
18
+ static [JSII_RTTI_SYMBOL_1] = { fqn: "@xpertss/projen-types.ActionSonarWorkflow", version: "0.0.19" };
19
19
  workflow;
20
20
  constructor(scope, options) {
21
21
  super(scope, 'ActionSonarWorkflow');