Skip to main content

Extending the Builder

Oidis Builder can be extended in two ways:

  1. Module scripts — external scripts hooked into the task lifecycle (prefer the existing Node.js runtime). Introducing another language or runtime requires a justified, approved exception under the technology selection policy.
  2. Custom tasks — TypeScript classes that integrate fully into the builder as first-class tasks

For simple integration, register a thin JavaScript module hook. Put substantial reusable logic in TypeScript and compile/check it through the existing Builder workflow. A small entry point does not require a custom Builder variant or a separate runtime.

See Module System for full details.

// package.conf.jsonp
"modules": {
"my-step": {
"script": "resource/scripts/my-step.js",
"events": { "before": "compile" }
}
}

Option 2: Custom Tasks (Full Integration)​

Custom tasks are TypeScript classes that extend BaseTask. They are discovered automatically by the builder's reflection system — just place them in the correct source directory and run oidis install.

Task Class Structure​

// source/typescript/Io/MyProject/Builder/Tasks/MyCustomTask.ts

import { BaseTask } from "@io-oidis-builder/Io/Oidis/Builder/Primitives/BaseTask.js";

export class MyCustomTask extends BaseTask {

protected getName() : string {
return "my-custom-task"; // this becomes the CLI task name
}

protected async processAsync($option? : string) : Promise<void> {
// $option is the part after ":" in "my-custom-task:option"

const version : string = this.project.version;
const src : string = this.properties.sources;

this.fileSystem.Write(
this.properties.projectBase + "/build/my-output.txt",
"Version: " + version + "\nSources: " + src
);

if ($option === "verbose") {
await this.terminal.SpawnAsync("echo", ["Verbose output enabled"]);
}

// Chain other built-in tasks:
await this.runTaskAsync("typescript:source");
}
}

Naming Convention​

The task name returned from getName() is what you use on the CLI:

oidis my-custom-task
oidis my-custom-task:verbose
tip

If you do not override getName(), the task name defaults to the class name lowercased without namespace (e.g., MyCustomTask → mycustomtask). Always override getName() to get a readable, hyphenated name.

Available Properties in BaseTask​

PropertyTypeDescription
this.projectIProjectFull project config from package.conf.jsonp
this.propertiesIPropertiesResolved build paths and capability flags
this.programArgsProgramArgsParsed CLI arguments
this.buildIBuildArgsBuild type, platform, timestamp
this.builderConfigIAppConfigurationBuilder's own global configuration
this.fileSystemFileSystemHandlerFile I/O operations
this.terminalTerminalShell command execution
this.externalModulesstringPath to <appdata>/external_modules

Available Methods in BaseTask​

MethodDescription
runTaskAsync($tasks)Execute other tasks by name (includes before/after hooks)
resolveExternConfig($name)Load a config from resource/configs/<name>.conf.jsonp
getDependenciesTree($option?)Override to declare task dependencies (advanced)

Using resolveExternConfig​

Load tool-specific configuration that can be overridden at project level:

protected async processAsync($option? : string) : Promise<void> {
// Loads from: <builder>/resource/configs/my-tool.conf.jsonp
// Overridden by: <project>/bin/project/configs/my-tool.conf.jsonp
// or <project>/resource/configs/my-tool.conf.json
const config : any = await this.resolveExternConfig("my-tool");

const rules : string[] = config.rules;
// ... use config
}

Using terminal.SpawnAsync​

Run shell commands from a task:

protected async processAsync() : Promise<void> {
const result = await this.terminal.SpawnAsync(
"my-tool",
["--input", this.properties.sources, "--output", this.properties.dest],
{
cwd: this.properties.projectBase,
advanced: { noExitOnStderr: true }
}
);
if (result.exitCode !== 0) {
// LogIt is from @io-oidis-commons
// do not use console.* — always use LogIt
}
}

Declaring Task Dependencies​

Override getDependenciesTree to declare which tasks must run first:

public getDependenciesTree($option? : string) : string[] {
if ($option === "with-compile") {
return ["compile:dev", "my-custom-task"];
}
return ["my-custom-task"];
}

This is used by orchestrator tasks to build the execution chain dynamically.


Registering Tasks in the Builder Itself​

If you are contributing to io-oidis-builder CORE:

  1. Create your task class in source/typescript/Io/Oidis/Builder/Tasks/<Category>/MyTask.ts
  2. Run oidis build dev — the build regenerates interface registrations (typescript-interfaces); oidis install does not
  3. The task is automatically discovered by Reflection.getAllClasses() on next startup

No imports or manual registration are needed anywhere.


Adding Project-Specific Tasks​

If your task is project-specific (not part of the builder CORE):

  1. Create the task class in your project's source directory
  2. The builder loads ALL Tasks/**/*.{js,mjs} files from its build output at startup
  3. After compilation, your task class is available alongside all built-in tasks
warning

Project-specific task classes must be compiled as part of the builder's source (they need access to @io-oidis-builder imports). This pattern is typically used only when building a custom builder variant.

For project-specific build logic, prefer Module Scripts — they don't require compiling into the builder.


Task Hooks via modules.register.json​

For the most common extension need (run something before/after a task), use the file-based hook system:

// <project>/resource/configs/modules.register.json
{
"generate-api": {
"script": "resource/scripts/generate-api.js",
"events": {
"before": "compile"
}
},
"post-package": {
"script": "resource/scripts/post-package.sh",
"interpreter": "bash",
"events": {
"after": "compress"
}
}
}

This file is loaded automatically — no changes to package.conf.jsonp needed.


Logging​

Always use LogIt from @io-oidis-commons inside tasks and scripts. Never use console.* directly:

import { LogIt } from "@io-oidis-commons/Io/Oidis/Commons/Utils/LogIt.js";

LogIt.Info("Starting code generation");
LogIt.Warning("Some optional dependency not found");
LogIt.Error("Code generation failed: " + error.message);
LogIt.Debug("Detailed debug info: " + JSON.stringify(data));

In module scripts (Node.js), you can use console.log / console.error — the builder captures stdout/stderr from child processes via its log pipeline.


See also: Tasks Reference | Module System | Builder Overview