Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
110 commits
Select commit Hold shift + click to select a range
2f790ba
feat(fs): Enhance API for incremental build, add tracking readers/wri…
RandomByte Nov 18, 2025
dc037e8
feat(server): Use incremental build in server
RandomByte Nov 18, 2025
6f56f13
feat(builder): Adapt tasks for incremental build
RandomByte Nov 18, 2025
fa3442e
refactor(project): Align getReader API internals with ComponentProjects
RandomByte Nov 18, 2025
f67c0b8
refactor(project): Refactor specification-internal workspace handling
RandomByte Nov 18, 2025
4532892
refactor(project): Implement basic incremental build functionality
RandomByte Nov 18, 2025
3a33579
refactor(cli): Use cache in ui5 build
RandomByte Nov 18, 2025
eecbcb3
refactor(project): Use cacache
RandomByte Nov 24, 2025
0aa4e26
refactor(project): Add cache manager
RandomByte Nov 28, 2025
0f1f0f4
refactor(fs): Refactor Resource internals
RandomByte Nov 27, 2025
2d0d8f0
refactor(fs): Provide createBuffer factory in FileSystem adapter
RandomByte Dec 1, 2025
ed04c7f
refactor(project): Refactor cache classes
RandomByte Dec 1, 2025
01646f9
refactor(fs): Add Proxy reader
RandomByte Dec 4, 2025
ec99c96
refactor(project): API refactoring
RandomByte Dec 8, 2025
070a313
refactor(builder): Rename task param 'buildCache' to 'cacheUtil'
RandomByte Dec 10, 2025
2ff58ea
refactor(project): Cleanup
RandomByte Dec 10, 2025
f90dabf
refactor(project): Move resource comparison to util
RandomByte Dec 12, 2025
fdc58e4
refactor(project): Refactor stage handling
RandomByte Dec 16, 2025
6033cd6
refactor(project): Fix cache handling
RandomByte Dec 16, 2025
037edd6
refactor(fs): Remove contentAccess mutex timeout from Resource
RandomByte Dec 16, 2025
cb193f4
refactor(project): Cleanup obsolete code/comments
RandomByte Dec 16, 2025
056650c
refactor(server): Cleanup obsolete code
RandomByte Dec 16, 2025
69dacc7
refactor(project): Rename watch handler events
RandomByte Dec 16, 2025
4f270f4
refactor: Fix linting issues
matz3 Dec 17, 2025
7fdb9a8
test(fs): Adjust getIntegrity tests
matz3 Dec 17, 2025
3d3bd92
refactor: Integrity handling
matz3 Dec 17, 2025
7ddd47c
test(fs): Adjust getIntegrity tests again
matz3 Dec 17, 2025
e44888a
refactor: Consider npm-shrinkwrap.json
matz3 Dec 17, 2025
8956372
refactor: Rename Tracker => MonitoredReader
RandomByte Dec 17, 2025
d5e8f91
refactor(project): Use workspace version in stage name
RandomByte Dec 17, 2025
e1a4d72
refactor(project): Fix stage writer order
RandomByte Dec 17, 2025
2081041
refactor(fs): Add Switch reader
RandomByte Dec 17, 2025
3b937f2
refactor(project): Cleanup WatchHandler debounce
RandomByte Dec 17, 2025
fc8df52
refactor(project): Fix outdated API call
RandomByte Dec 17, 2025
222df46
refactor(project): Fix build signature calculation
RandomByte Dec 17, 2025
81bd912
refactor(fs): Pass integrity to cloned resource
RandomByte Dec 17, 2025
02bdfe3
refactor(project): Fix pattern matching and resource comparison
RandomByte Dec 17, 2025
bf40958
refactor(project): Import/overwrite stages from cache after saving
RandomByte Dec 18, 2025
4f70a8f
test(builder): Sort files/folders
matz3 Dec 19, 2025
3fb4d75
refactor(builder): Prevent duplicate entries on app build from cache
matz3 Dec 19, 2025
3fa4d7f
refactor(fs): Refactor MonitorReader API
RandomByte Dec 23, 2025
4d584ce
refactor(fs): Always calculate integrity on clone
RandomByte Dec 24, 2025
b6d821d
refactor(fs): Add getter to WriterCollection
RandomByte Dec 29, 2025
39d21dc
refactor(builder): Remove cache handling from tasks
RandomByte Dec 24, 2025
e2e6aa1
refactor(builder): Add env variable for disabling workers
RandomByte Dec 30, 2025
11ec37b
refactor(project): Track resource changes using hash trees
RandomByte Dec 23, 2025
42e37b5
refactor(project): Compress cache using gzip
RandomByte Jan 2, 2026
0937ee4
refactor(fs): Ensure writer collection uses unique readers
RandomByte Jan 2, 2026
8294181
refactor(fs): Remove write tracking from MonitoredReaderWriter
RandomByte Jan 2, 2026
b2bfb29
refactor(project): Identify written resources using stage writer
RandomByte Jan 2, 2026
85828e7
refactor(project): Add basic differential update functionality
RandomByte Jan 2, 2026
97ea3d7
refactor(builder): Re-add cache handling in tasks
RandomByte Jan 7, 2026
0042996
refactor(project): Cleanup HashTree implementation
RandomByte Jan 7, 2026
9a0d9d3
refactor(project): Make WatchHandler wait for build to finish before …
RandomByte Jan 7, 2026
51b6363
refactor(project): Use cleanup hooks in update builds
RandomByte Jan 7, 2026
abf3aba
refactor(logger): Log skipped projects/tasks info in grey color
RandomByte Jan 7, 2026
dc3c03e
refactor(project): Fix cache update mechanism
RandomByte Jan 7, 2026
7c9de68
refactor(project): WatchHandler emit error event
RandomByte Jan 7, 2026
4b780c5
refactor(server): Exit process on rebuild error
RandomByte Jan 7, 2026
2d1288c
refactor(project): Fix delta indices
RandomByte Jan 7, 2026
2c451a3
refactor(logger): Support differential update task logging
RandomByte Jan 8, 2026
b8ada97
refactor(project): Provide differential update flag to logger
RandomByte Jan 8, 2026
56b4f1b
refactor(server): Log error stack on build error
RandomByte Jan 8, 2026
66cdbae
refactor(project): Add chokidar
RandomByte Jan 8, 2026
4071460
refactor(project): Limit build signature to project name and config
RandomByte Jan 8, 2026
400158f
refactor(project): Improve ResourceRequestGraph handling
RandomByte Jan 8, 2026
dd566cc
refactor(server): Remove obsolete code from serveResources middleware
matz3 Jan 8, 2026
3dfe66e
refactor(project): Add env variable to skip cache update
RandomByte Jan 9, 2026
6b6ba85
refactor(project): Fix hash tree updates
RandomByte Jan 9, 2026
f3dbb50
refactor(project): Remove unused 'cacheDir' param
matz3 Jan 9, 2026
0d96ab3
fix(project): Prevent projects from being always invalidated
matz3 Jan 9, 2026
39f6fb0
fix(project): Prevent exception when not building in watch mode
matz3 Jan 12, 2026
218280e
refactor(project): Only store new or modified cache entries
RandomByte Jan 9, 2026
7931836
refactor(project): Refactor project cache validation
RandomByte Jan 11, 2026
a4ba6db
refactor(project): Extract project build into own method
matz3 Jan 13, 2026
3e00d78
fix(project): Clear cleanup task queue
matz3 Jan 13, 2026
abe9e6d
test(project): Add ProjectBuilder integration test
matz3 Jan 13, 2026
9133660
test(project): Add failing ProjectBuilder test case
matz3 Jan 13, 2026
d33a314
test(project): Enhance ProjectBuilder test assertions
matz3 Jan 13, 2026
8dec87c
test(project): Add library test case for ProjectBuilder
matz3 Jan 13, 2026
2b14042
test(project): Build dependencies in application test of ProjectBuilder
matz3 Jan 13, 2026
d6af7cd
test(project): Refactor ProjectBuilder test code
matz3 Jan 14, 2026
1fe328d
refactor(project): Refactor task resource request tracking
RandomByte Jan 14, 2026
723ea92
test(project): Add theme-library test and update assertions for fixed…
matz3 Jan 14, 2026
c5b050f
test(project): Use graph.build for ProjectBuilder test
matz3 Jan 15, 2026
2ebf9cb
test(project): Add custom task to ProjectBuilder test
matz3 Jan 15, 2026
b4830ba
fix(project): Fix custom task execution
matz3 Jan 15, 2026
6758d64
fix(project): Prevent writing cache when project build was skipped
matz3 Jan 16, 2026
9165b2a
refactor(project): Create dedicated SharedHashTree class
RandomByte Jan 15, 2026
044ca6d
refactor(project): Re-implement differential task build
RandomByte Jan 15, 2026
d6e9c43
refactor(project): Fix HashTree tests
RandomByte Jan 16, 2026
267907f
refactor(project): Fix handling cache handling of removed resources
RandomByte Jan 16, 2026
10c55c6
test(project): Add cases for theme.library.e with seperate less files
maxreichmann Jan 20, 2026
94d8829
refactor(project): Update graph traversal
RandomByte Jan 20, 2026
83919bf
refactor(project): Add BuildServer and BuildReader
RandomByte Jan 16, 2026
c64e5cc
refactor(project): Refactor project result cache
RandomByte Jan 20, 2026
866c680
refactor(server): Integrate BuildServer
RandomByte Jan 20, 2026
b884487
refactor(project): Small build task cache restructuring, cleanup
RandomByte Jan 20, 2026
23fa96b
refactor(project): JSDoc cleanup
RandomByte Jan 20, 2026
4a43272
refactor(project): ProjectBuilder cleanup
RandomByte Jan 20, 2026
3540ee5
refactor(builder): Small stringReplacer cleanup
RandomByte Jan 20, 2026
5acfa09
revert(fs): Add Switch reader
RandomByte Jan 20, 2026
f858659
refactor(project): Add perf logging, cleanups
RandomByte Jan 20, 2026
2bf0409
refactor(project): Add cache write perf logging
RandomByte Jan 20, 2026
dc968e1
refactor(project): Improve stage change handling
RandomByte Jan 21, 2026
fa275ed
refactor(project): Implement queue system in BuildServer
matz3 Jan 21, 2026
6b8cb11
refactor(server): Add error callback and handle in CLI
RandomByte Jan 21, 2026
51a745f
refactor(project): ProjectBuilder to provide callback on project built
RandomByte Jan 21, 2026
323e81f
refactor(project): Do not always include root project in build
RandomByte Jan 21, 2026
48ff98f
refactor(project): Refactor BuildServer init, add tests
RandomByte Jan 21, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4,165 changes: 239 additions & 3,926 deletions package-lock.json

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion packages/builder/lib/processors/nonAsciiEscaper.js
Original file line number Diff line number Diff line change
Expand Up @@ -83,8 +83,8 @@ async function nonAsciiEscaper({resources, options: {encoding}}) {
// only modify the resource's string if it was changed
if (escaped.modified) {
resource.setString(escaped.string);
return resource;
}
return resource;
}

return Promise.all(resources.map(processResource));
Expand Down
3 changes: 2 additions & 1 deletion packages/builder/lib/processors/stringReplacer.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,10 @@ export default function({resources, options: {pattern, replacement}}) {
return Promise.all(resources.map(async (resource) => {
const content = await resource.getString();
const newContent = content.replaceAll(pattern, replacement);
// only modify the resource's string if it was changed
if (content !== newContent) {
resource.setString(newContent);
return resource;
}
return resource;
}));
}
2 changes: 1 addition & 1 deletion packages/builder/lib/tasks/buildThemes.js
Original file line number Diff line number Diff line change
Expand Up @@ -192,7 +192,7 @@ export default async function({
}

let processedResources;
const useWorkers = !!taskUtil;
const useWorkers = !process.env.UI5_CLI_NO_WORKERS && !!taskUtil;
if (useWorkers) {
const threadMessageHandler = new FsMainThreadInterface(fsInterface(combo));

Expand Down
13 changes: 10 additions & 3 deletions packages/builder/lib/tasks/escapeNonAsciiCharacters.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,17 +14,24 @@ import nonAsciiEscaper from "../processors/nonAsciiEscaper.js";
*
* @param {object} parameters Parameters
* @param {@ui5/fs/DuplexCollection} parameters.workspace DuplexCollection to read and write files
* @param {string[]} [parameters.changedProjectResourcePaths] Set of changed resource paths within the project.
* This is only set if a cache is used and changes have been detected.
* @param {object} parameters.options Options
* @param {string} parameters.options.pattern Glob pattern to locate the files to be processed
* @param {string} parameters.options.encoding source file encoding either "UTF-8" or "ISO-8859-1"
* @returns {Promise<undefined>} Promise resolving with <code>undefined</code> once data has been written
*/
export default async function({workspace, options: {pattern, encoding}}) {
export default async function({workspace, changedProjectResourcePaths, options: {pattern, encoding}}) {
if (!encoding) {
throw new Error("[escapeNonAsciiCharacters] Mandatory option 'encoding' not provided");
}

const allResources = await workspace.byGlob(pattern);
let allResources;
if (changedProjectResourcePaths) {
allResources = await Promise.all(changedProjectResourcePaths.map((resource) => workspace.byPath(resource)));
} else {
allResources = await workspace.byGlob(pattern);
}

const processedResources = await nonAsciiEscaper({
resources: allResources,
Expand All @@ -33,5 +40,5 @@ export default async function({workspace, options: {pattern, encoding}}) {
}
});

await Promise.all(processedResources.map((resource) => workspace.write(resource)));
await Promise.all(processedResources.map((resource) => resource && workspace.write(resource)));
}
19 changes: 15 additions & 4 deletions packages/builder/lib/tasks/minify.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ import fsInterface from "@ui5/fs/fsInterface";
* @param {object} parameters Parameters
* @param {@ui5/fs/DuplexCollection} parameters.workspace DuplexCollection to read and write files
* @param {@ui5/project/build/helpers/TaskUtil|object} [parameters.taskUtil] TaskUtil
* @param {string[]} [parameters.changedProjectResourcePaths] Set of changed resource paths within the project.
* This is only set if a cache is used and changes have been detected.
* @param {object} parameters.options Options
* @param {string} parameters.options.pattern Pattern to locate the files to be processed
* @param {boolean} [parameters.options.omitSourceMapResources=false] Whether source map resources shall
Expand All @@ -26,17 +28,26 @@ import fsInterface from "@ui5/fs/fsInterface";
* @returns {Promise<undefined>} Promise resolving with <code>undefined</code> once data has been written
*/
export default async function({
workspace, taskUtil, options: {pattern, omitSourceMapResources = false, useInputSourceMaps = true
}}) {
const resources = await workspace.byGlob(pattern);
workspace, taskUtil, changedProjectResourcePaths,
options: {pattern, omitSourceMapResources = false, useInputSourceMaps = true}
}) {
let resources;
if (changedProjectResourcePaths) {
resources = await Promise.all(changedProjectResourcePaths.map((resource) => workspace.byPath(resource)));
} else {
resources = await workspace.byGlob(pattern);
}
if (resources.length === 0) {
return;
}
const processedResources = await minifier({
resources,
fs: fsInterface(workspace),
taskUtil,
options: {
addSourceMappingUrl: !omitSourceMapResources,
readSourceMappingUrl: !!useInputSourceMaps,
useWorkers: !!taskUtil,
useWorkers: !process.env.UI5_CLI_NO_WORKERS && !!taskUtil,
}
});

Expand Down
38 changes: 21 additions & 17 deletions packages/builder/lib/tasks/replaceBuildtime.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,26 +28,30 @@ function getTimestamp() {
*
* @param {object} parameters Parameters
* @param {@ui5/fs/DuplexCollection} parameters.workspace DuplexCollection to read and write files
* @param {string[]} [parameters.changedProjectResourcePaths] Set of changed resource paths within the project.
* This is only set if a cache is used and changes have been detected.
* @param {object} parameters.options Options
* @param {string} parameters.options.pattern Pattern to locate the files to be processed
* @returns {Promise<undefined>} Promise resolving with <code>undefined</code> once data has been written
*/
export default function({workspace, options: {pattern}}) {
export default async function({workspace, changedProjectResourcePaths, options: {pattern}}) {
let resources;
if (changedProjectResourcePaths) {
resources = await Promise.all(changedProjectResourcePaths.map((resource) => workspace.byPath(resource)));
} else {
resources = await workspace.byGlob(pattern);
}
const timestamp = getTimestamp();

return workspace.byGlob(pattern)
.then((processedResources) => {
return stringReplacer({
resources: processedResources,
options: {
pattern: "${buildtime}",
replacement: timestamp
}
});
})
.then((processedResources) => {
return Promise.all(processedResources.map((resource) => {
return workspace.write(resource);
}));
});
const processedResources = await stringReplacer({
resources,
options: {
pattern: "${buildtime}",
replacement: timestamp
}
});
return Promise.all(processedResources.map((resource) => {
if (resource) {
return workspace.write(resource);
}
}));
}
40 changes: 23 additions & 17 deletions packages/builder/lib/tasks/replaceCopyright.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,32 +24,38 @@ import stringReplacer from "../processors/stringReplacer.js";
*
* @param {object} parameters Parameters
* @param {@ui5/fs/DuplexCollection} parameters.workspace DuplexCollection to read and write files
* @param {string[]} [parameters.changedProjectResourcePaths] Set of changed resource paths within the project.
* This is only set if a cache is used and changes have been detected.
* @param {object} parameters.options Options
* @param {string} parameters.options.copyright Replacement copyright
* @param {string} parameters.options.pattern Pattern to locate the files to be processed
* @returns {Promise<undefined>} Promise resolving with <code>undefined</code> once data has been written
*/
export default function({workspace, options: {copyright, pattern}}) {
export default async function({workspace, changedProjectResourcePaths, options: {copyright, pattern}}) {
if (!copyright) {
return Promise.resolve();
return;
}

// Replace optional placeholder ${currentYear} with the current year
copyright = copyright.replace(/(?:\$\{currentYear\})/, new Date().getFullYear());

return workspace.byGlob(pattern)
.then((processedResources) => {
return stringReplacer({
resources: processedResources,
options: {
pattern: /(?:\$\{copyright\}|@copyright@)/g,
replacement: copyright
}
});
})
.then((processedResources) => {
return Promise.all(processedResources.map((resource) => {
return workspace.write(resource);
}));
});
let resources;
if (changedProjectResourcePaths) {
resources = await Promise.all(changedProjectResourcePaths.map((resource) => workspace.byPath(resource)));
} else {
resources = await workspace.byGlob(pattern);
}

const processedResources = await stringReplacer({
resources,
options: {
pattern: /(?:\$\{copyright\}|@copyright@)/g,
replacement: copyright
}
});
return Promise.all(processedResources.map((resource) => {
if (resource) {
return workspace.write(resource);
}
}));
}
37 changes: 21 additions & 16 deletions packages/builder/lib/tasks/replaceVersion.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,25 +14,30 @@ import stringReplacer from "../processors/stringReplacer.js";
*
* @param {object} parameters Parameters
* @param {@ui5/fs/DuplexCollection} parameters.workspace DuplexCollection to read and write files
* @param {string[]} [parameters.changedProjectResourcePaths] Set of changed resource paths within the project.
* This is only set if a cache is used and changes have been detected.
* @param {object} parameters.options Options
* @param {string} parameters.options.pattern Pattern to locate the files to be processed
* @param {string} parameters.options.version Replacement version
* @returns {Promise<undefined>} Promise resolving with <code>undefined</code> once data has been written
*/
export default function({workspace, options: {pattern, version}}) {
return workspace.byGlob(pattern)
.then((allResources) => {
return stringReplacer({
resources: allResources,
options: {
pattern: /\$\{(?:project\.)?version\}/g,
replacement: version
}
});
})
.then((processedResources) => {
return Promise.all(processedResources.map((resource) => {
return workspace.write(resource);
}));
});
export default async function({workspace, changedProjectResourcePaths, options: {pattern, version}}) {
let resources;
if (changedProjectResourcePaths) {
resources = await Promise.all(changedProjectResourcePaths.map((resource) => workspace.byPath(resource)));
} else {
resources = await workspace.byGlob(pattern);
}
const processedResources = await stringReplacer({
resources,
options: {
pattern: /\$\{(?:project\.)?version\}/g,
replacement: version
}
});
await Promise.all(processedResources.map((resource) => {
if (resource) {
return workspace.write(resource);
}
}));
}
4 changes: 2 additions & 2 deletions packages/builder/test/utils/fshelper.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@ export async function readFileContent(filePath) {
}

export async function directoryDeepEqual(t, destPath, expectedPath) {
const dest = await readdir(destPath, {recursive: true});
const expected = await readdir(expectedPath, {recursive: true});
const dest = (await readdir(destPath, {recursive: true})).sort();
const expected = (await readdir(expectedPath, {recursive: true})).sort();
t.deepEqual(dest, expected);
}

Expand Down
2 changes: 2 additions & 0 deletions packages/cli/lib/cli/commands/build.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import baseMiddleware from "../middlewares/base.js";
import path from "node:path";

const build = {
command: "build",
Expand Down Expand Up @@ -173,6 +174,7 @@ async function handleBuild(argv) {
const buildSettings = graph.getRoot().getBuilderSettings() || {};
await graph.build({
graph,
cacheDir: path.join(graph.getRoot().getRootPath(), ".ui5-cache"),
destPath: argv.dest,
cleanDest: argv["clean-dest"],
createBuildManifest: argv["create-build-manifest"],
Expand Down
6 changes: 5 additions & 1 deletion packages/cli/lib/cli/commands/serve.js
Original file line number Diff line number Diff line change
Expand Up @@ -146,7 +146,10 @@ serve.handler = async function(argv) {
serverConfig.cert = cert;
}

const {h2, port: actualPort} = await serverServe(graph, serverConfig);
const {promise: pOnError, reject} = Promise.withResolvers();
const {h2, port: actualPort} = await serverServe(graph, serverConfig, function(err) {
reject(err);
});

const protocol = h2 ? "https" : "http";
let browserUrl = protocol + "://localhost:" + actualPort;
Expand Down Expand Up @@ -183,6 +186,7 @@ serve.handler = async function(argv) {
const {default: open} = await import("open");
open(browserUrl);
}
await pOnError; // Await errors that should bubble into the yargs handler
};

export default serve;
49 changes: 49 additions & 0 deletions packages/fs/lib/MonitoredReader.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import AbstractReader from "./AbstractReader.js";

export default class MonitoredReader extends AbstractReader {
#reader;
#sealed = false;
#paths = [];
#patterns = [];

constructor(reader) {
super(reader.getName());
this.#reader = reader;
}

getResourceRequests() {
this.#sealed = true;
return {
paths: this.#paths,
patterns: this.#patterns,
};
}

async _byGlob(virPattern, options, trace) {
if (this.#sealed) {
throw new Error(`Unexpected read operation after reader has been sealed`);
}
if (this.#reader.resolvePattern) {
const resolvedPattern = this.#reader.resolvePattern(virPattern);
this.#patterns.push(resolvedPattern);
} else {
this.#patterns.push(virPattern);
}
return await this.#reader._byGlob(virPattern, options, trace);
}

async _byPath(virPath, options, trace) {
if (this.#sealed) {
throw new Error(`Unexpected read operation after reader has been sealed`);
}
if (this.#reader.resolvePath) {
const resolvedPath = this.#reader.resolvePath(virPath);
if (resolvedPath) {
this.#paths.push(resolvedPath);
}
} else {
this.#paths.push(virPath);
}
return await this.#reader._byPath(virPath, options, trace);
}
}
Loading
Loading