This document contains all information related to the kie-tools
as a Git repository. It describes its directory
structure, concepts like "packages" and specifics on important tools like Maven, TypeScript, and Container images.
File | Description |
---|---|
packages/ examples/ |
Where the source code is. There’s no distinction between these two dirs, except from the name and the convention as to where to put stuff. No dangling files are allowed inside these dirs, only other dirs containing packages. There is no package hierarchy. Both dirs have a flat structure. |
repo/ scripts/ |
Related to the monorepo itself. repo contains files describing the monorepo structure (I.e., packages DAG, for enabling partial clones with sparse checkout). scripts contains code to make the monorepo run smoothly. (E.g., update-version-to or bootstrap ). |
docs/ gifs/ |
Files referenced externally in READMEs, for instance. Could be unified into a single directory only, probably with a better name. |
package.json pnpm-workspace.yaml |
Together they define the monorepo structure and make some commands available at the root dir. The root package.json acts like the glue holding everything together. It declares dependencies that are necessary for the monorepo to operate. Packages inside scripts are part of these dependencies. |
.envrc devbox.lock devbox.json |
Direnv and Devbox configuration files. See these instructions to set it up. See NIX_DEV_ENV.md |
.ci/ |
Jeknins configuration for Apache release jobs |
.github/ |
GitHub configuration |
.asf.yaml |
Apache Software Foundation (ASF) YAML for configuring GitHub features, as no one has access to the Settings tab of repos under the Apache organization. |
.vscode/ _intellij-project/ |
IDE configuration |
.gitattributes .gitignore packages/*/.gitignore examples/*/.gitignore |
Git stuff. .gitignore files must either by at the root of the repo, or at the root of a package. |
.husky/ prettier.config.js .prettierignore packages/*/.prettierignore examples/*/.prettierignore |
Code formatters configuration. Done: Prettier defaults, package.json, XML To do: Java, Go, Containerfiles, Python |
.npmrc .syncpackrc.json patches/ pnpm-lock.yaml |
Related to NPM dependencies. .npmrc addresses pnpm/pnpm#6300 and prevents 3rd party package binaries inside node_modules to access packages they don’t declare as dependencies via extend-note-path=false. Syncpack makes sure all package.json files declare the same version of dependencies. patches contains manual patches we do to NPM dependencies on node_modules/.pnpm pnpm-lock.yaml keeps NPM dependencies pinned to a specific version to make the monorepo future-proof. |
.build-env-root |
Marker file used to not go looking for build-env configurations outside of the monorepo. More details about build-env below. |
The monorepo is built on top of the Package
abstraction, defined by package.json
files. This is borrowed from NPM, and in fact, important for using pnpm
as a script runner. A package is the smallest unit present in the monorepo. Configuration should always be done through environment variables, which can be read and consumed by virtually anything. All build configurations and parameters are defined as Environment Variables and accessed exclusively through build-env
, a small CLI tool we built.
Packages
package.json
$schema
(required)- Always points to
<root>/repo/package.json.schema.json
- Allows for better IDE support for properties on this file.
- Always points to
name
(required)- Type: string
- Can be plain (my-package) or scoped (@my-scope/my-package). Usually separated by dashes.
- See https://docs.npmjs.com/cli/v10/configuring-npm/package-json#name for more details.
version
(required)- Type: string
- In SemVer format.
- Should never be edited manually. It is
0.0.0
onmain
. - To update it, run this command at the root:
- pnpm update-version-to [new-version] [pnpm filter?]
private
(optional)- Type: boolean
- Whether this package should be published to NPM.
scripts → build:dev
(required)- Type: string (
bash
and/orcmd
commands) - Builds skipping everything. Used to build upstream.
- Type: string (
scripts → build:prod
(required)- Type: string (
bash
and/orcmd
commands) - Builds targeting a release.
- Type: string (
scripts → install
(optional)- Type: string (
bash
and/orcmd
commands) - Runs when the root bootstrap script installs dependencies.
- Type: string (
kieTools → requiredPreinstalledCliCommands
(required)- Type: string[]
- List of commands necessary to build this package.
dependencies
(optional)- Type: Map<string, string>
- Lists the RUNTIME dependencies of this package.
- Dependencies from inside the monorepo have version
"workspace:*"
. This is apnpm
feature. - Dependencies from outside the monorepo have fixed versions.
- Dependencies do come transitively when users do
npm install @my-scope/my-package
.
devDependencies
(optional)- Type: Map<string, string>
- Lists the DEVELOPMENT / BUILD dependencies of this package.
- Dependencies from inside the monorepo have version
"workspace:*"
. This is apnpm
feature. - Dependencies from outside the monorepo have fixed versions.
- devDependencies don’t come transitively when users do
npm install @my-scope/my-package
.
- Any other property accepted by NPM
- Only relevant for NPM packages, of course.
- See https://docs.npmjs.com/cli/v10/configuring-npm/package-json
env/index.js
- Use if you need to configure
build:dev
andbuild:prod
scripts. (E.g., skipping tests or changing the port of an app) - Mapping of environment variables to JSON
- We never read the environment variables directly. Always through the JSON defined by
env/index.js
- Scoped per-package. Can’t access other packages by mistake.
- Use if you need to configure
dist*
directories- Where build results usually end up.
- …and whatever else the package needs
- Other than the
package.json
and the correct place to put test reports in JUnit XML format, packages are free to structure themselves in any way they need. - Refer to Conventions to know more about how packages are structured.
- Other than the
Environment variables
- Everything is configurable through environment variables
- Platform-agnostic
- CI-friendly
build-env
is a utility script for easily maintaining the set of used environment variables by each packagebuild-env myPackage.myEnvVar
will print the value mapped byenv/index.js
.- It goes up recursively on the directory structure looking for
env/index.js
files. - Once it finds one, or the
.build-env-root
marker file, it stops. - There’s no automatic merging. Combining
env/index.js
files is done manually. Composition over inheritance!
0.0.0
is the development version atmain
- SemVer-compatible.
- Will absolutely never be resolved as "higher" than any published version.
- Other branches will follow their stream name, with
999
replacing the variable part. E.g.,10.0.x
branch has version10.0.999
.
package.json → version
is the source of truth for a package version- It is always in SemVer format.
- All language/tool-specific files must be derived from whatever version is defined on
package.json
. This ensures that theupdate-version
script works as intended, as it simply updates thepackage.json
version and runs abootstrap
. - Defining an
install
script that reads frompackage.json
is the best way to configure language/tool-specific files that contain a version. - Maven packages, for example, rely on the Maven recommended CI-friendly setup. This is also compatible with the upcoming Maven 4.
- If your technology needs a version in another format different from SemVer, you’ll need to derive it from the SemVer string present on
package.json → version
build:dev
andbuild:prod
scripts must produce everything that dependent packages might need- The difference between them is the amount of expensive checks/optimizations they do, but they must be able to produce a full version of the package equally.
- An example is running linters or triggering compiler optimizations. In the very beginning,
build:dev
used to be calledbuild:fast
, so that kind of gives an idea of why there are two scripts. - Build:prod should be understood as a "full build", where everything you possibly want to do when building a package is done.
- It’s not uncommon for build:dev and build:prod scripts to be identical.
build:dev
andbuild:prod
must be "thread-safe"- Building packages in parallel is one of the things that makes the monorepo fast. Always assume other packages are building at the same time as your package.
- If you follow the rules above, you’ll hardly hit a problem, unless you’re using a tool/command that can’t be run in parallel. In this case, finding an alternative way to achieve the results you want is going to be necessary, as we can’t assume a package will be built in isolation.
- As of April 2024, the monorepo CI runs on the free tier of GitHub Actions, where machines have 2 available cores. Due to memory constraints, we run a sequential build. Locally, however, everyone has multiple cores available, and usually a lot of memory, so most people do
pnpm -r build:dev
locally after bootstrapping.
- Environment variables are exclusively read through
build-env
, never directly.- This ensures encapsulation and good reports during
pnpm bootstrap
.
- This ensures encapsulation and good reports during
- There are no nested packages. Each package has one, and one only,
package.json
file.- The package structure does not allow nested packages.
- The monorepo has a flat structure architecture.
- Packages can’t reference other monorepo files outside of their directories using
..
on paths- Packages should only have knowledge about its own directory structure, never upwards.
- Use
node_modules
, don’t ever do../my-other-package/some/nested/dir
. Do./node_modules/my-other-package/some/nested/dir
, or./node_modules/@my-scope/my-package/some/nested/dir
. - This ensures packages only have access to what they declare as dependencies on their
package.json
files.
- Packages can’t mutate the monorepo’s file system outside of its directory structure
- The
node_modules
directory has a lot of symbolic links to directories that are shared between all packages. So changing it can potentially pollute the global scope and result in unpredictable behavior. - As a general rule, see the
node_modules
dir as read-only.
- The
- Generated code is either ignored or versioned on Git.
- If files are produced either during
bootstrap
, they must be either ignored by Git, or committed. Runningpnpm bootstrap
on a clean clone must never change any versioned file. If files are generated duringbuild
, they should not be versio in Git.
- If files are produced either during
- Test results must be reported in JUnit XML format
- JUnit XMLs became the de-facto standard for test result reporting.
- Having every test case reported using this format means we’ll always get good introspection and compatibility with test management tools, like Buildkite or TestRail.
- Define the
package.json → kieTools → requiredPreinstalledCliCommands
property with what your package needs to be built.- This won’t fail any of the steps, but there’s a nice report on the
bootstrap
script that helps users understand their environment.
- This won’t fail any of the steps, but there’s a nice report on the
- Prefer downloading stuff on scripts other than
install
- Ideally, any external resource you might need for the build should be fetched during the
bootstrap
script. Packages can hook into the root-levelbootstrap
script by defining aninstall
script on their package.json file. - This allows the monorepo build to fail faster when there’s a problem fetching an external resource.
- Ideally, any external resource you might need for the build should be fetched during the
- Make sure your package is buildable and testable on Windows, macOS, and Linux
- macOS x86 is being discontinued, but GitHub Actions still uses it, and unfortunately we can’t use the new Arm-based runners, as they’re not compatible with Docker yet.
- Follow the environment variables naming convention.
- As of April 2024, the convention is
PACKAGE_NAME__configurationName
; but - In the future, it will be better to change this to
KIE_TOOLS__packageName__configurationName
.
- As of April 2024, the convention is
- Use the same name for the package directory and
package.json → name
- In practice, the name of the directory doesn’t have meaning, but helps us navigate the repo.
- Don’t use mutable versions anywhere
latest
,dev
, or-SNAPSHOT
versions change with time, and can make the build break unexpectedly.- More than that, when building an older tag or commit, pointing to
latest
will likely not work. - Beware of unspecified versions too, as they often default to
latest
.
- Configure dependencies to have the same single version available to all packages
- Pretty much like https://opensource.google/documentation/reference/thirdparty/oneversion
- Try to keep the version string in a single place, or at least enforce it to be exactly the same as other declarations, like we do with Syncpack in the CI for NPM dependencies.
- Keep packages small
- Smaller packages can be installed, built, tested, and packaged much faster, and contribute positively to parallelization when running tasks
- Make sure
package.json → build:dev
andpackage.json → start
are friendly to debugging.- Check if source maps are available.
- Check if obfuscation is turned off.
A few scripts are available for general purpose usage on kie-tools
. They're built prior to everything else and are available in the top-level node_modules
dir, meaning any script can access them. They don't need to be declared as dependencies too since they're already a dependency of the root package.json
. All of them can be invoked with pnpm [script-name]
.
- Used for everyday development
- bootstrap: Runs when we execute
pnpm bootstrap
. - build-env: Environment variables management.
- run-script-if: Shell-friendly conditional command execution
- bootstrap: Runs when we execute
- Used when cloning
kie-tools
- sparse-checkout: Partially clone
kie-tools
and work on a subset of its packages.
- sparse-checkout: Partially clone
- Mostly used by our automations
- check-junit-report-results: Checks JUnit XML files to see if tests failed. Used on our CI.
- update-kogito-version: Updates the version of Kogito Maven dependencies. (E.g.,
999-20250105-SNAPSHOT
,999-SNAPSHOT
or10.0.0
) - update-stream-name: Updates this repo's stream name. (E.g.,
main
or10.0.x
) - update-version: Updates this repo's version (E.g.,
0.0.0
or10.0.999
)
To comply with the repository's conventions and concepts, like Packages, Maven usage has been mapped and documented here for reference.
Foundational package for other Maven-based packages to base themselves on.
Used for writing .mvn/maven.config
with -Drevision
, -Dmaven.repo.local.tail
and other necessary properties.
And for centralized <dependencyManagement>
and other necessary standard configurations
All Maven-based packages should declare it as a dependency
on their package.json
files and its pom.xml
as parent.
<parent>
<groupId>org.kie</groupId>
<artifactId>kie-tools-maven-base</artifactId>
<version>${revision}</version>
<relativePath>./node_modules/@kie-tools/maven-base/pom.xml</relativePath>
</parent>
Due to CI friendly versions as well. Present on maven-base
already, no need to configure it individually.
https://www.mojohaus.org/flatten-maven-plugin/
Has to be at least node install.js
, using @kie-tools/maven-base
to set up properties like -Drevision
, -Dmaven.repo.local.tail
.
Has to be at least mvn clean install -DskipTests=$(build-env tests.run --not) -Dmaven.test.failure.ignore=$(build-env tests.ignoreFailures)
Needs to install so that other Maven repos can reference it.
Has to be at least mvn clean deploy -DdeployAtEnd -Dmaven.deploy.skip=$(build-env maven.deploy.skip) -DskipTests=$(build-env tests.run --not) -Dmaven.test.failure.ignore=$(build-env tests.ignoreFailures)
This is important for the Release jobs to correctly deploy it. Of course, deploy is skipped by default, so it acts like mvn clean install ...
.
If your Maven package depends on other Maven packages, you need to declare those in the dependencies
section, not on devDependencies
.
This ensures the release scripts know what other Maven packages need to be published alongside yours, and makes it easier to configure -Dmaven.repo.local.tail
via buildTailFromPackageJsonDependencies()
This section contains relevant topics about developing packages hosted on KIE Tools.
- Nix.dev, Devbox, and
direnv
(recommended!)- See NIX_DEV_ENV.md
- Traditional
- IntelliJ IDEA
- VS Code
- // TODO
Usually, packages that can be developed individually (E.g., apps of any kind, or libraries with dedicated development apps) define a script called start
on their package.json
, for example, developing the DMN Editor can be done in these steps:
pnpm bootstrap -F dmn-editor...
pnpm -F dmn-editor^... build:dev
pnpm -F dmn-editor start
Packages that don’t contain an app (so-called libraries) can be developed in conjunction with apps that can be run. When developing libraries, they need to be rebuilt so that apps can be re-run with the changed version of the libraries you’re developing, and you can see the changes in effect. To do that, you can keep track of the libraries you’re building, and simply rebuild them with:
pnpm -F my-library build:dev
If you’re changing multiple libraries and don’t want to rebuild one by one, you can do:
pnpm -F my-app^... build:dev
This is much less error-prone, as you won’t forget to build one of the libraries that you might’ve changed.
If your app doesn’t support live-reloading, you’ll need to re-run it too. If it does, rebuilding the libraries is enough.
Some webpack-based apps can be run with a special --env live parameter, so that rebuilding libraries is not necessary. This is great for developing the Boxed Expression Editor using the dmn-editor
Storybook,
pnpm -F dmn-editor start --env live
Simply add a directory with a package.json
inside it, give it the properties you need and run pnpm bootstrap
at the root dir. It is going to be part of the build now.
If this package is public (I.e., doesn’t define "private": "true" on its package.json
), it means it is published to the NPM registry, and there might be people depending on it. We should always publish a last version with an updated README telling people that the package was deleted, and what they should do about it.
Ideally, we should deprecate packages before completely removing them.
Private packages are never consumed directly, and can be removed without any additional process. The build will fail if there are references to the removed package that weren’t removed too.
Should be treated as removing and adding it. Don’t forget to rename the package directory AND package.json → name
.
If this package is public, consider leaving behind a package with the old name containing deprecation notices, and pointing to the new name.
Adding a dependency to an existing package
Change package.json
manually and run pnpm bootstrap
at the root directory.
In practice, you don’t need to worry about this, as a pre-commit hook will format the changed files when you do git commit
.
The CI will also check if your code is properly formatted.
- Conventionally,
build:dev
doesn’t run tests. - If you really need to do
build:prod
locally, you can use the following environment variables to skip tests, as they usually take a while to run:export KIE_TOOLS_BUILD__runTests=false
orexport KIE_TOOLS_BUILD__runEndToEndTests=false
- Tests won’t run at all.
export KIE_TOOLS_BUILD__ignoreTestFailures=true
orexport KIE_TOOLS_BUILD__ignoreEndToEndTestFailures=true
- Tests will run, but a failing test won’t fail the build.
pnpm list --depth Infinity --only-projects [pnpm filter?]
- Will display the relationship the filtered packages have with other packages inside the repo.
- This is very similar to
mvn dependency:tree
.
It’s really important to understand that the software toolchain has flaws, and we’re all exposed to downloading malicious code from time to time.
There are, however, some things we can do to prevent ourselves from being in a dangerous situation.
- Dependency management tools, like Maven,
pnpm
, orpip
, have mechanisms to help us be safer.- Lock files
- Commit SHA references
- Fixed versions
- Checksums
- When adding a dependency to the monorepo, be thoughtful about who’s behind that dependency and what methods you’re using to download it.
- Prefer projects that are very widely known, active and have a healthy community around it.
- Think about the scope of this dependency.
- Is this going to be used for development; or
- Will users need to install it to be able to run our packages?
- Licenses
Feel free to proposed changes to this manual by sending a PR directly to this file.