workflow-build-runner is a small Node.js runtime for repository-local build.js
files that replace legacy build.xml driven workflows.
It provides:
defineBuild(...)for declaring a build definitionsequence([...])for ordered target executionbuildhelpers for filesets, filesystem actions, checksums, XML properties and path helpersworkflowTargetswith the current Geonovum workflow target implementationsworkflowTargets.fs.*target factories for declarative filesystem stepsworkflowTargets.custom.xslt(...)for repository-local XSLT steps with CLI context pathsgithubhelpers for repository filesets and GitHub push/pull actions, kept separate from the core runner- a
workflow-build-runnerCLI for executing a build definition
npm install @geonovum/workflow-build-runnerUse workflowBuild.build.* when a target needs custom logic. These helpers are
plain async functions, so they fit best inside an async (context) => { ... }
target.
Complete example:
const workflowBuild = require("@geonovum/workflow-build-runner");
module.exports = workflowBuild.defineBuild({
defaultTarget: "main",
targets: {
main: async (context) => {
const files = await workflowBuild.build.fileset({
dir: context.inputDir,
include: ["**/*.xml", "**/*.xsl"],
exclude: ["node_modules/**", "dist/**"],
});
await workflowBuild.build.makeDir(context.outputDir);
await workflowBuild.build.copyFile(
`${context.inputDir}/config.xml`,
`${context.outputDir}/config.xml`,
);
await workflowBuild.build.copyDir(
`${context.buildRoot}/assets`,
`${context.outputDir}/assets`,
);
const hash = await workflowBuild.build.checksum(
`${context.outputDir}/config.xml`,
"sha256",
);
const props = await workflowBuild.build.xmlProperty(
`${context.outputDir}/config.xml`,
);
context.state.files = files;
context.state.configHash = hash;
context.state.config = props;
},
},
});The fileset(...) helper returns items shaped as:
{
path: "relative/path.xml",
sourcePath: "/absolute/source/path.xml",
meta: { source: "local", dir: "/absolute/root" },
}Available helpers:
fileset({ dir, include, exclude }): select local files with glob patternsmakeDir(dirName): create a directory recursivelydeleteDir(dirName): delete a directory recursivelycopyFile(sourceFileName, destinationFileName): copy one filecopyDir(sourceDirName, destinationDirName): copy a directory recursivelydeleteFile(fileName): delete one file if it existszip(sourceDirName, destinationFileName): zip a directoryunzip(sourceFileName, destinationDirName): extract a zip filechecksum(fileName, algorithm = "sha256"): return a file hashbasename(fileFullname): return the filename without extensionxslt({ stylesheetFile, sefFile, sourceFile, outputFile, resultDocuments, stylesheetParams, sefCacheDir }): run a direct XSLT transformationcompileStylesheet(stylesheetFile, sefCacheDir): compile an XSLT stylesheet to SEFnormalizeStylesheet(sourceFileName): normalize a stylesheet text for SaxonJS usecreateGeneratedStylesheet({ sourcePath, targetPath, patcher }): write a generated stylesheet copyxmlProperty(fileName): read XML into dotted propertiesnormalizeRelativePath(...),sanitizePathSegment(...),toFileHref(...): path utilities
Use workflowTargets.fs.* when a build step is just a filesystem action and
does not need custom JavaScript. These functions return build targets and can be
used directly in targets.
const workflowBuild = require("@geonovum/workflow-build-runner");
module.exports = workflowBuild.defineBuild({
defaultTarget: "main",
targets: {
init: workflowBuild.workflowTargets.fs.makeDir({
dir: (context) => context.outputDir,
}),
assets: workflowBuild.workflowTargets.fs.copyDir({
source: "assets",
destination: "assets",
}),
zipResult: workflowBuild.workflowTargets.fs.zip({
source: (context) => context.outputDir,
destination: "result.zip",
}),
cleanTemp: workflowBuild.workflowTargets.fs.deleteDir({
dir: (context) => context.tempDir,
}),
main: workflowBuild.sequence(["init", "assets", "zipResult", "cleanTemp"]),
},
});Relative source paths resolve against buildRoot. Relative destinations resolve
against outputDir for copy/zip targets and against tempDir for unzip targets.
Available filesystem target factories:
workflowTargets.fs.makeDir({ dir })workflowTargets.fs.deleteDir({ dir })workflowTargets.fs.copyFile({ source, destination })workflowTargets.fs.copyDir({ source, destination })workflowTargets.fs.deleteFile({ file })workflowTargets.fs.zip({ source, destination })workflowTargets.fs.unzip({ source, destination })
"use strict";
const workflowBuild = require("@geonovum/workflow-build-runner");
module.exports = workflowBuild.defineBuild({
defaultTarget: "main",
targets: {
init: workflowBuild.workflowTargets.word.common.init,
unzip: workflowBuild.workflowTargets.word.common.unzip,
ruimop: workflowBuild.workflowTargets.word.common.ruimop,
config: workflowBuild.workflowTargets.word.common.config,
respec: workflowBuild.workflowTargets.word.markdown.respec,
main: workflowBuild.sequence(["init", "unzip", "ruimop", "config", "respec"])
}
});Generic XSLT target example:
const workflowBuild = require("@geonovum/workflow-build-runner");
module.exports = workflowBuild.defineBuild({
defaultTarget: "main",
targets: {
transform: workflowBuild.workflowTargets.custom.xslt({
stylesheet: "mijn-transform.xsl",
source: "input.xml",
output: "output.html",
params: {
"mijn-param": "waarde",
},
}),
main: workflowBuild.sequence(["transform"]),
},
});Direct XSLT helper example:
await workflowBuild.build.xslt({
stylesheetFile: `${context.buildRoot}/mijn-transform.xsl`,
sourceFile: `${context.inputDir}/input.xml`,
outputFile: `${context.outputDir}/output.html`,
stylesheetParams: {
"mijn-param": "waarde",
},
sefCacheDir: context.sefCacheDir,
});Use workflowTargets.custom.xslt(...) when the transformation is a declarative
target in targets. Use workflowBuild.build.xslt(...) inside an async target
when you need custom control flow around the transformation.
Path resolution for workflowTargets.custom.xslt(...):
stylesheetis resolved relative tobuildRootsourceis resolved relative toinputDiroutputis resolved relative tooutputDirparamsaccepts either an inline object or a JSON file path relative tobuildRoot
xsl:result-document outputs are supported through SaxonJS baseOutputURI.
Relative result-document hrefs are written relative to the configured output
file location. Keep result-document hrefs relative; use filesystem helpers if
generated files need to be moved afterwards.
workflowTargets.xml.xmlProperty(...) reads a full XML file into dotted state
keys using a real XML parser:
targets: {
config: workflowBuild.workflowTargets.xml.xmlProperty({
sourceFile: (context) => path.join(context.tempDir, "config.xml"),
stateKey: "config",
}),
}For <config id="demo"><title>Demo</title></config> this stores:
context.state.config["config.id"] === "demo";
context.state.config["config.title"] === "Demo";Attributes are stored as properties below the element name. Nested elements are joined with dots:
<config id="demo">
<document>
<title>Demo</title>
</document>
</config>Stores:
context.state.config["config.id"] === "demo";
context.state.config["config.document.title"] === "Demo";workflowTargets.xml.readProperties(...) remains available for explicit
mappings and now uses the XML property parser before falling back to the legacy
tag reader.
GitHub operations are exported separately under workflowBuild.github. They are
not mixed into workflowTargets because they need network access, GitHub App
credentials and repository permissions.
The intended authentication model is a GitHub App. Pass auth.appId,
auth.privateKey and, when known, auth.installationId. If installationId is
omitted, the helper tries to resolve the installation from organisation and
repo, or from organisation for organisation-level actions.
const githubAuth = {
appId: process.env.GITHUB_APP_ID,
privateKey: process.env.GITHUB_APP_PRIVATE_KEY,
installationId: process.env.GITHUB_APP_INSTALLATION_ID,
};A pre-created installation access token can still be passed as token, but that
is mainly useful when another part of the system already handles GitHub App
authentication.
Pull selected files from a GitHub repository:
const repositoryFiles = await workflowBuild.github.buildRepositoryFileset({
organisation: "mijn-org",
repo: "demo-repo",
auth: githubAuth,
branch: "main",
include: "src/**",
});
await workflowBuild.github.pullFileset({
targetDir: "./download",
fileset: repositoryFiles,
readContent: workflowBuild.github.createGitHubFileReader({
organisation: "mijn-org",
repo: "demo-repo",
auth: githubAuth,
branch: "main",
}),
});Push selected local files to a GitHub repository:
const localFiles = await workflowBuild.build.fileset({
dir: "./publicatie",
include: "**/*",
});
await workflowBuild.github.pushFileset({
organisation: "mijn-org",
repo: "demo-repo",
auth: githubAuth,
branch: "main",
fileset: localFiles,
message: "Publiceer gegenereerde bestanden",
targetPrefix: "docs",
});The GitHub module also exposes createRepository(...). The GitHub App
installation must have suitable repository and organisation permissions for the
operation being performed.
workflow-build-runner \
--buildfile path/to/build.js \
--repo path/to/workflow-repo \
--input path/to/input \
--output path/to/output \
--temp path/to/tempThe CLI also accepts a build.xml path and resolves it to the sibling build.js.
- This package depends on
saxon-js,xslt3andzip-lib. - Some workflows try to use the
tidybinary when generating XHTML snapshots. If it is not available, the runner falls back to copying the HTML file.
This project is licensed under Apache-2.0. See LICENSE.
Local validation:
npm run prepublish:checkVersioning:
npm run changesetPublishing is handled by GitHub Actions through Changesets:
- pushes to
maincreate or update the version PR, or publish when a version commit is merged - the
Releaseworkflow can also be started manually withworkflow_dispatch