Skip to content

Migrate build system from Maven to Node.js/Gulp/semantic-release#69

Open
joewiz wants to merge 10 commits intoeXist-db:masterfrom
joewiz:migrate-to-node-build
Open

Migrate build system from Maven to Node.js/Gulp/semantic-release#69
joewiz wants to merge 10 commits intoeXist-db:masterfrom
joewiz:migrate-to-node-build

Conversation

@joewiz
Copy link
Member

@joewiz joewiz commented Feb 21, 2026

Summary

  • Replaces Apache Maven build with Node.js + Gulp for building XAR packages, modeled on the approach used in the roaster project
  • Adds semantic-release for fully automated versioning and GitHub releases triggered by conventional commits
  • Drops eXist-db 4.x support (breaking change); minimum version is now 5.0.0
  • Preserves all existing XQSuite tests, now run via XML-RPC against a Docker eXist-db service in CI (or local instance for development)

Key changes

  • New build toolchain: package.json, gulpfile.js, expath-pkg.xml.tmpl, repo.xml.tmpl
  • Automated releases: .releaserc configures semantic-release to bump versions, commit package.json, and publish XAR as a GitHub release asset
  • Automated changelog: scripts/update-repo-changelog.js — called by semantic-release's prepare step; reads conventional commits since the last tag (via conventional-commits-parser) and inserts a new <change> entry into repo.xml.tmpl
  • Test runner: test/run-tests.js — Node.js script that invokes XQSuite via @existdb/node-exist (XML-RPC) and parses results with @xmldom/xmldom
  • CI: .github/workflows/build.yml replaces old ci.yml + ci-docker-dev.yml; runs against eXist-db latest, release, and 5.0.0 Docker images using Node.js LTS
  • Conventional commits: commitlint + husky enforce the Conventional Commits spec locally via a commit-msg hook
  • Ant wrapper: build.xml for backward compatibility (ant xarnpm run build)
  • File layout: src/main/xquery/semver.xqmcontent/semver.xqm; src/test/xquery/*.xqmtest/*.xqm; icon.png moved to root
  • Removed: pom.xml, xar-assembly.xml, xquery-license-style.xml, src/
  • Updated: .gitignore (Maven → Node.js entries), dependabot.yml (maven → npm), README.md (build/test/release instructions)

The feat! commit uses the conventional commit format with a BREAKING CHANGE: footer, so semantic-release will bump from 3.0.14.0.0 when merged to master.

Test plan

  • npm install succeeds
  • npm run build produces dist/semver-xq-3.0.1.xar
  • CI workflow triggers on push and runs against all three eXist-db versions
  • With a local eXist-db: cp .env.example .env && npm test passes all XQSuite tests
  • On merge to master: semantic-release creates a 4.0.0 GitHub release with XAR attached

🤖 Generated with Claude Code

@joewiz joewiz marked this pull request as draft February 21, 2026 18:20
@joewiz joewiz force-pushed the migrate-to-node-build branch 3 times, most recently from a3401ae to 9efd098 Compare February 22, 2026 14:28
@joewiz joewiz marked this pull request as ready for review February 22, 2026 14:44
@joewiz joewiz changed the title feat!: migrate build system from Maven to Node.js/Gulp/semantic-release Migrate build system from Maven to Node.js/Gulp/semantic-release Feb 22, 2026
@joewiz
Copy link
Member Author

joewiz commented Feb 22, 2026

Development notes for reviewers

This PR was developed iteratively with CI feedback. Here's a summary of the key decisions and problems solved, to help reviewers understand the shape of the implementation.

roaster as the model

Throughout development we used the roaster project as the reference implementation. The gulpfile structure, template handling, connection options, and CI approach are all closely modeled on roaster. Where we diverged (e.g., the test runner), we noted the reasons.

Why HTTPS on port 8443 (not HTTP on 8080)

@existdb/gulp-exist v5 defaults to secure: true, which means HTTPS. Attempts to override this to HTTP caused SSL connection errors in CI because node-exist's compatibility shim converts the secure boolean to a protocol string, overriding any http: protocol set via environment variable. Rather than fighting this, we aligned with roaster's approach: roaster's CI also uses port 8443 (HTTPS) with no EXISTDB_SERVER override, relying on the default. This is why build.yml maps port 8443 and run-tests.js defaults to https://localhost:8443 with rejectUnauthorized: false for the self-signed cert that eXist-db's Docker image ships with.

expath-pkg.xml

The <file> path in the <xquery> component declaration is just semver.xqm (not content/semver.xqm) because eXist-db's EXPath resolver automatically prepends the package's content/ directory — using content/semver.xqm causes a content/content/semver.xqm not found error in eXist-db 5.0.0 (though 6.x is more forgiving). This was confirmed by consulting roaster's expath-pkg.xml.tmpl.

XQSuite test runner

The test runner uses inspect:module-functions(xs:anyURI("xmldb:exist:///db/apps/semver-xq/tests/...")) to collect test functions and passes them to test:suite(). Two things to note:

  • The xmldb:exist:/// URI scheme is required — bare /db/... paths return an empty sequence from inspect:module-functions(), causing test:suite() to fail with a cardinality error. This pattern is taken from xbow's test runner.
  • The XQuery is submitted via eXist-db's REST API using the XML query document format (Content-Type: application/xml, <query xmlns="http://exist.sourceforge.net/NS/exist"> wrapper) rather than form-encoded POST, which proved more reliable.

Commit structure

The branch has two commits to keep the diff readable:

  1. chore: restructure repository layout — pure file moves and deletions, no functional changes
  2. feat!: replace Maven build with Node.js/Gulp/semantic-release — all new tooling, with BREAKING CHANGE: footer to trigger a 4.0.0 release via semantic-release on merge

🤖 Generated with Claude Code

@joewiz joewiz force-pushed the migrate-to-node-build branch from 343980e to 8af6469 Compare February 22, 2026 15:05
@joewiz joewiz mentioned this pull request Feb 22, 2026
6 tasks
@joewiz joewiz requested a review from a team February 22, 2026 15:59
@joewiz joewiz added the enhancement New feature or request label Feb 22, 2026
["@semantic-release/github", {
"assets": [{
"path": "dist/semver-xq-*.xar",
"name": "semver-xq-${nextRelease.version}.xar",
Copy link
Contributor

@duncdrum duncdrum Feb 23, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For uploads to GitHub having xar files without version numbers *e.g.(semver-xq.xar) actually helps with automated consumption. For uploads to public-repo you need xar files with version numbers. Just an observation to make sure this is what your want.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am strongly in favour of keeping the version numbers.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the note. Given line-o's strong preference for versioned filenames (see below), we're keeping the current naming — which, as you point out, is the right choice for GitHub release assets. [-Claude]

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed — versioned filenames it is. Thank you for the clear steer. [-Claude]

<type>library</type>
<changelog>
<change version="3.0.0">
<ul xmlns="http://www.w3.org/1999/xhtml">
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should the maven switch appear here? I would a release to be triggered after merging

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point. We've added scripts/update-repo-changelog.js (commit 5c6bfa5), which semantic-release calls during its prepare step. It reads conventional commits since the last tag, groups them by type, and inserts a new <change> element into repo.xml.tmpl using @xmldom/xmldom for proper XML DOM manipulation — no string editing of XML. The template is then committed alongside package.json in the release commit, so a 4.0.0 changelog entry will be generated automatically when this PR merges. [-Claude]

Copy link
Member Author

@joewiz joewiz Feb 23, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@line-o It was originally going to use string manipulation, but I asked Claude to find a less brittle method. I mentioned XSLT, and it said this would add a dependency of xsltproc, and instead recommended a node-native approach of xmldom, which I decided to go with. Looks clean enough to me. I'm open to other ideas, of course.

@line-o
Copy link
Member

line-o commented Feb 23, 2026

The changelog creation is the one thing that is still lacking in the current gulp-exist setup.

@line-o
Copy link
Member

line-o commented Feb 23, 2026

this PR would fix #13

@line-o
Copy link
Member

line-o commented Feb 23, 2026

The test output has the string "unknown" where I would expect the test suite name. Maybe this can be improved.

Running XQSuite tests...
  [PASS] unknown: 0 tests, 0 failures, 0 errors
  [PASS] unknown: 8 tests, 0 failures, 0 errors
  [PASS] unknown: 22 tests, 0 failures, 0 errors
  [PASS] unknown: 14 tests, 0 failures, 0 errors
  [PASS] unknown: 1 tests, 0 failures, 0 errors
  [PASS] unknown: 3 tests, 0 failures, 0 errors
  [PASS] unknown: 7 tests, 0 failures, 0 errors

Copy link
Member

@line-o line-o left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A really good start! There are some loose ends and a few thing we should discuss.

```
- `feat:` → minor version bump
- `fix:` → patch version bump
- `feat!:` or `BREAKING CHANGE:` footer → major version bump
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unless we set up this repository to explicitly use conventional commits the ! will not be recognised as an indicator for breaking changes.
semantic-release uses Angular Commit Message format by default.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point. To confirm: @semantic-release/commit-analyzer uses the Angular preset by default, which does recognise \! per the Conventional Commits specification. That said, you're right that it should be made explicit. We've added @commitlint/config-conventional + a Husky commit-msg hook in commit 2d122e7 to enforce the convention locally, and updated the README to reference the spec. [-Claude]

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I fear that my previous statement was misunderstood.

Unless we set up this repository to explicitly use conventional commits the ! will not be recognised as an indicator for breaking changes.

The commit-analyser used by semantic-release must be configured to use conventional commits.

Addtionally, a commit linting action could be added to the GitHub actions. An example for such a setup can be found in https://github.com/eXist-db/xst/blob/main/.github/workflows/commitlint.yml

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the clarification, @line-o — addressed in commit cdcb9be. Both @semantic-release/commit-analyzer and @semantic-release/release-notes-generator are now configured with "preset": "conventionalcommits", with conventional-changelog-conventionalcommits added as a dependency. The commitlint.yml workflow is also in place, modeled on https://github.com/eXist-db/xst. [-Claude]

@@ -0,0 +1,147 @@
#!/usr/bin/env node
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sweet! We need this in a easy-to-use form so that all our libs and apps can benefit from this.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed! ;)

@line-o
Copy link
Member

line-o commented Feb 23, 2026

this PR will close #24

@line-o
Copy link
Member

line-o commented Feb 23, 2026

For some reason no actions are run anymore. -- my hunch was wrong --

@joewiz
Copy link
Member Author

joewiz commented Feb 23, 2026

Agreed, and addressed in commit 5c6bfa5. We've added scripts/update-repo-changelog.js, which semantic-release calls as part of its prepare step: it reads conventional commits since the last tag, groups them by type, and inserts a new <change> element into repo.xml.tmpl using @xmldom/xmldom for proper XML DOM manipulation. The updated template is then committed alongside package.json on each release. [-Claude]

@joewiz
Copy link
Member Author

joewiz commented Feb 23, 2026

Yes — the new build.yml runs the test suite against existdb/existdb:latest, existdb/existdb:release, and existdb/existdb:5.0.0 on every push, which directly addresses the multi-version CI coverage described in #13. [-Claude]

joewiz and others added 4 commits February 23, 2026 11:22
Move source and test files out of the Maven src/ directory tree:
- src/main/xquery/semver.xqm → content/semver.xqm
- src/main/xar-resources/icon.png → icon.png
- src/test/xquery/*.xqm → test/*.xqm

Remove Maven build files: pom.xml, xar-assembly.xml, xquery-license-style.xml,
and the src/test/java and src/test/resources Maven test harness.

Remove old CI workflows: ci.yml, ci-docker-dev.yml.

Update .gitignore (Maven → Node.js entries) and dependabot.yml (maven → npm).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Replaces the Apache Maven build and test system with Node.js + Gulp,
modeled on the approach used in the roaster project
(https://github.com/eeditiones/roaster).

Build system:
- package.json: project metadata, npm scripts, devDependencies
- gulpfile.js: tasks for clean, build, install, test (ESM, gulp v5)
- expath-pkg.xml.tmpl + repo.xml.tmpl: XAR package descriptor templates
  processed by @existdb/gulp-replace-tmpl
- build.xml: Ant wrapper for backward compatibility (ant xar → npm run build)
- .releaserc: semantic-release config for automated versioning and GitHub
  releases triggered by conventional commits
- .env.example: template for local eXist-db connection settings

CI:
- .github/workflows/build.yml: runs against eXist-db latest, release,
  and 5.0.0 Docker images (HTTPS port 8443, matching roaster's approach);
  release job runs semantic-release on master merges

Test runner:
- test/run-tests.js: Node.js script that invokes XQSuite via eXist-db's
  REST API using inspect:module-functions() (as used in xbow), parses
  the XML response, and exits 1 on any failures or errors

BREAKING CHANGE: Minimum eXist-db version is now 5.0.0.
eXist-db 4.x is no longer supported.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

fix: migrate changelog from xar-assembly.xml to repo.xml.tmpl

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- actions/checkout v4 → v6.0.2
- actions/setup-node v4 → v6.2.0
- node-version '18' (EOL) → 'lts/*' in both build and release jobs

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds scripts/update-repo-changelog.js, called by semantic-release's
prepareCmd, which reads conventional commits since the last tag, groups
them by type (breaking changes, features, fixes), and inserts a new
<change> element at the top of the <changelog> in repo.xml.tmpl using
@xmldom/xmldom for proper XML DOM manipulation.

repo.xml.tmpl is added to @semantic-release/git assets so the updated
template is committed alongside package.json on each release.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@joewiz joewiz force-pushed the migrate-to-node-build branch from 2d122e7 to 49ff1d3 Compare February 23, 2026 16:22
joewiz and others added 2 commits February 23, 2026 11:57
Replace axios + raw REST API with @existdb/node-exist v7:
- Use getXmlRpcClient() + db.queries.readAll() via XML-RPC
- Connection options read from env via readOptionsFromEnv();
  localhost HTTPS rejectUnauthorized handled automatically
- Parse JUnit response with @xmldom/xmldom instead of regex;
  fall back to package attribute when testsuite name is empty
- Remove axios devDependency; add @existdb/node-exist explicitly

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds @commitlint/cli, @commitlint/config-conventional, and husky.
The commit-msg hook runs commitlint on every commit, ensuring messages
follow the Conventional Commits spec as expected by semantic-release.
README updated to mention the convention and link to the spec.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@joewiz joewiz force-pushed the migrate-to-node-build branch from 26e3ca6 to 8d4cf18 Compare February 23, 2026 16:58
joewiz and others added 2 commits February 23, 2026 12:29
Replace hand-rolled regex in update-repo-changelog.js with CommitParser
from conventional-commits-parser (already a transitive dependency via
semantic-release) for more robust and spec-compliant parsing.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
XQSuite XML-RPC output leaves the testsuite name and package attributes
empty; fall back to extracting the last path/fragment segment from the
classname attribute of the first testcase element.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@joewiz
Copy link
Member Author

joewiz commented Feb 23, 2026

The test output has the string "unknown" where I would expect the test suite name. Maybe this can be improved.

Running XQSuite tests...
  [PASS] unknown: 0 tests, 0 failures, 0 errors
  [PASS] unknown: 8 tests, 0 failures, 0 errors
  [PASS] unknown: 22 tests, 0 failures, 0 errors
  [PASS] unknown: 14 tests, 0 failures, 0 errors
  [PASS] unknown: 1 tests, 0 failures, 0 errors
  [PASS] unknown: 3 tests, 0 failures, 0 errors
  [PASS] unknown: 7 tests, 0 failures, 0 errors

@line-o With Claude's latest commits, now this appears as follows:

Running XQSuite tests...
  [PASS] http://exist-db.org/xquery/semver/test/coerce: 8 tests, 0 failures, 0 errors
  [PASS] http://exist-db.org/xquery/semver/test/compare: 24 tests, 0 failures, 0 errors
  [PASS] http://exist-db.org/xquery/semver/test/expath-package-semver-template: 14 tests, 0 failures, 0 errors
  [PASS] http://exist-db.org/xquery/semver/test/serialize: 1 tests, 0 failures, 0 errors
  [PASS] http://exist-db.org/xquery/semver/test/sort: 3 tests, 0 failures, 0 errors
  [PASS] http://exist-db.org/xquery/semver/test/parse: 7 tests, 0 failures, 0 errors

Is that what you had in mind?

And to you and @duncdrum I think Claude and I have addressed all questions now—hopefully in not too annoying a fashion.

Here's the log of my session (search for to see my prompts): https://gist.github.com/joewiz/6243291159fadeaeb1ce739deb686d60

Configure @semantic-release/commit-analyzer and release-notes-generator
to use the conventionalcommits preset, add the required
conventional-changelog-conventionalcommits dependency, and add a GitHub
Actions workflow to lint commit messages on PRs.

Modeled on https://github.com/eXist-db/xst.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@line-o
Copy link
Member

line-o commented Feb 26, 2026

The changelog generation code would benefit from being even more tightly integrated with the underlying library conventional-changelog
From my research it seems possible to overwrite / customize the templates used to generate the standard changelog.md.
In a perfect world we would create and maintain our own changelog generator - making programmatic use of the writer - that reads and extends a repo.xml.tmpl and commits the amended version to the repository and adds the repo.xml to the build artifact.

…rator

Replace the hand-rolled commit grouping logic in update-repo-changelog.js
with programmatic use of conventional-changelog-writer, driven by the
conventional-changelog-conventionalcommits preset.

The preset now supplies both parserOpts (consistent with semantic-release)
and writerOpts (transform, groupBy, sorting). A custom Handlebars template
replaces the default markdown output with plain <li> lines that @xmldom/xmldom
inserts into repo.xml.tmpl as before.

Breaking-change commits carry notes that the writer surfaces via noteGroups.
To avoid rendering both the note and the commit line, our transform wrapper
sets a temporary isBreaking flag on the in-memory commit object; the template
uses {{#unless isBreaking}} to suppress the redundant commit line, so only
the note text appears (prefixed "Breaking change:") in the XML output.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@joewiz
Copy link
Member Author

joewiz commented Feb 26, 2026

The changelog generation code would benefit from being even more tightly integrated with the underlying library conventional-changelog … making programmatic use of the writer — that reads and extends a repo.xml.tmpl and commits the amended version to the repository and adds the repo.xml to the build artifact.

Done in commit 94b1204. update-repo-changelog.js now loads both parserOpts and writerOpts from the conventional-changelog-conventionalcommits preset (the same one semantic-release uses internally), so commit classification is fully consistent with the rest of the toolchain.

A custom Handlebars template replaces the default markdown output with plain <li> lines. The preset's transform is wrapped to inject a prefix field (New, Fix, Improvement, Revert) on each commit based on its section title, which the template renders as <li>{{prefix}}: {{subject}}</li>. Breaking-change notes surface via noteGroups as <li>Breaking change: …</li>; to avoid a duplicate commit line, the transform also sets a temporary isBreaking flag on the in-memory commit object, and the template uses {{#unless isBreaking}} to suppress it. The resulting <li> strings are then parsed by @xmldom/xmldom and inserted into repo.xml.tmpl exactly as before. [-Claude]

@joewiz
Copy link
Member Author

joewiz commented Feb 26, 2026

@line-o p.s. Here's the session that produced the most recent commit and comment addressing your comment from earlier today: https://gist.github.com/joewiz/3af7c6c88c87e46a6ae0dec2fff734f2.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants