Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
29 changes: 9 additions & 20 deletions packages/cli/lib/cli/commands/cache.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,9 @@
import chalk from "chalk";
import path from "node:path";
import os from "node:os";
import process from "node:process";
import {isLogLevelEnabled} from "@ui5/logger";
import baseMiddleware from "../middlewares/base.js";
import Configuration from "@ui5/project/config/Configuration";
import FrameworkCache from "@ui5/project/internal/ui5Framework/cache";
import CacheManager from "@ui5/project/internal/build/cache/CacheManager";
import {getUi5DataDirOrDefault, formatPath} from "../../dataDir.js";
import {
CACHE_CLEAN_HELP_USAGE,
displayCacheCleanWarning,
Expand Down Expand Up @@ -63,20 +60,6 @@ async function getConfirmation(argv) {
});
}

async function resolveCacheUi5DataDir() {
// TODO: Consolidate ui5DataDir resolution once PR #1456 follow-up cleanup is done.
// Keep behavior aligned with existing main-branch resolution order.
let ui5DataDir = process.env.UI5_DATA_DIR;
if (!ui5DataDir) {
const config = await Configuration.fromFile();
ui5DataDir = config.getUi5DataDir();
}
if (ui5DataDir) {
return path.resolve(process.cwd(), ui5DataDir);
}
return path.join(os.homedir(), ".ui5");
}

function withAbsPath(entries, ui5DataDir) {
return entries.map((entry) => {
return {...entry, absPath: getAbsPath(ui5DataDir, entry)};
Expand All @@ -91,12 +74,18 @@ function getAbsPath(ui5DataDir, cacheEntry) {
}

async function handleCache(argv) {
const ui5DataDir = await resolveCacheUi5DataDir();
// Lazy loading to prevent unnecessary imports when the command is not executed
const [{default: FrameworkCache}, {default: CacheManager}] = await Promise.all([
import("@ui5/project/internal/ui5Framework/cache"),
import("@ui5/project/internal/build/cache/CacheManager"),
]);

const ui5DataDir = await getUi5DataDirOrDefault({cwd: process.cwd()});
const isVerbose = isLogLevelEnabled("verbose");

if (isVerbose) {
// logger.verbose pollutes output with framework noise.
process.stderr.write(`Checking cache at ${chalk.bold(ui5DataDir)} …\n`);
process.stderr.write(`Checking cache at ${chalk.bold(formatPath(ui5DataDir))} …\n`);
}

const [frameworkInfo, buildInfo] = await Promise.all([
Expand Down
5 changes: 3 additions & 2 deletions packages/cli/lib/cli/commands/helpers/cacheOutput.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import chalk from "chalk";
import process from "node:process";
import {formatPath} from "../../../dataDir.js";

const GROUP_FRAMEWORK = "Framework";
const GROUP_BUILD = "Build";
Expand Down Expand Up @@ -47,14 +48,14 @@ function writeCategoryHeader(title) {

function writePreviewItem(absPath, detail) {
process.stderr.write(
` ${PREVIEW_MARKER} ${chalk.dim(absPath)}` +
` ${PREVIEW_MARKER} ${chalk.dim(formatPath(absPath))}` +
`${detail ? ` ${ITEM_DIVIDER} ${detail}` : ""}\n`
);
}

function writeCleanupItem(absPath, detail) {
process.stderr.write(
` ${SUCCESS_MARKER} Removed ${chalk.dim(absPath)}` +
` ${SUCCESS_MARKER} Removed ${chalk.dim(formatPath(absPath))}` +
`${detail ? ` ${ITEM_DIVIDER} ${detail}` : ""}\n`
);
}
Expand Down
57 changes: 57 additions & 0 deletions packages/cli/lib/dataDir.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import path from "node:path";
import os from "node:os";
import process from "node:process";
import Configuration from "@ui5/project/config/Configuration";

// TODO: This module only consolidates ui5DataDir resolution within the cli package.
// A general, cross-package cleanup of ui5DataDir resolution is tracked in PR #1456.

/**
* Resolves the UI5 data directory from the <code>UI5_DATA_DIR</code> environment variable or the
* UI5 configuration. The environment variable takes precedence over the configured value.
*
* @param {object} options
* @param {string} options.cwd Directory a relative data-dir value is resolved against
* @returns {Promise<string|undefined>} Absolute path to the UI5 data directory,
* or <code>undefined</code> when neither source provides a value
*/
export async function getUi5DataDir({cwd}) {
let ui5DataDir = process.env.UI5_DATA_DIR;
if (!ui5DataDir) {
const config = await Configuration.fromFile();
ui5DataDir = config.getUi5DataDir();
}
return ui5DataDir ? path.resolve(cwd, ui5DataDir) : undefined;
}

/**
* Like {@link getUi5DataDir}, but falls back to <code>&lt;home&gt;/.ui5</code> when neither the
* environment variable nor the configuration provides a value.
*
* @param {object} options
* @param {string} options.cwd Directory a relative data-dir value is resolved against
* @returns {Promise<string>} Absolute path to the UI5 data directory
*/
export async function getUi5DataDirOrDefault({cwd}) {
return (await getUi5DataDir({cwd})) ?? path.join(os.homedir(), ".ui5");
}

/**
* Shortens an absolute path for display by replacing the user's home directory with
* <code>~</code> (e.g. <code>~/.ui5</code>). Intended for console and
* error output only — never for values used in actual filesystem operations.
*
* @param {string} filePath Path to format for display
* @returns {string} The path with the home directory replaced by <code>~</code>, or the
* original path when it does not reside within the home directory
*/
export function formatPath(filePath) {
const home = os.homedir();
if (filePath === home) {
return "~";
}
if (filePath.startsWith(home + path.sep)) {
return "~" + filePath.slice(home.length);
}
return filePath;
}
13 changes: 1 addition & 12 deletions packages/cli/lib/framework/utils.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import path from "node:path";
import {graphFromStaticFile, graphFromPackageDependencies} from "@ui5/project/graph";
import Configuration from "@ui5/project/config/Configuration";
import {getUi5DataDir} from "../dataDir.js";

export async function getRootProjectConfiguration(projectGraphOptions) {
let graph;
Expand Down Expand Up @@ -49,16 +48,6 @@ export async function frameworkResolverResolveVersion({frameworkName, frameworkV
});
}

async function getUi5DataDir({cwd}) {
// ENV var should take precedence over the dataDir from the configuration.
let ui5DataDir = process.env.UI5_DATA_DIR;
if (!ui5DataDir) {
const config = await Configuration.fromFile();
ui5DataDir = config.getUi5DataDir();
}
return ui5DataDir ? path.resolve(cwd, ui5DataDir) : undefined;
}

const utils = {
getRootProjectConfiguration,
getFrameworkResolver,
Expand Down
57 changes: 19 additions & 38 deletions packages/cli/test/lib/cli/commands/cache.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,9 @@ function getDefaultArgv() {
};
}

// Stable absolute path used as the resolved ui5DataDir in most tests
const TEST_UI5_DATA_DIR = path.resolve("test-ui5-home");
// Stable absolute path used as the resolved ui5DataDir in most tests. Anchored outside the home
// directory so path assertions are not affected by the ~ shortening applied to home-dir paths.
const TEST_UI5_DATA_DIR = path.join(path.resolve(path.sep), "test-ui5-home");

// Typical framework stub result shape: { path, libraryCount, versionCount }
const FRAMEWORK_STUB = {path: "framework", libraryCount: 18, versionCount: 5};
Expand All @@ -45,10 +46,7 @@ test.beforeEach(async (t) => {
// Prevent real env var from leaking into tests
delete process.env.UI5_DATA_DIR;

t.context.configurationGetUi5DataDirStub = sinon.stub().returns(TEST_UI5_DATA_DIR);
t.context.configurationFromFileStub = sinon.stub().resolves({
getUi5DataDir: t.context.configurationGetUi5DataDirStub,
});
t.context.getUi5DataDirOrDefaultStub = sinon.stub().resolves(TEST_UI5_DATA_DIR);

t.context.frameworkCacheGetCacheInfo = sinon.stub();
t.context.frameworkCacheCleanCache = sinon.stub();
Expand All @@ -62,11 +60,6 @@ test.beforeEach(async (t) => {
t.context.yesnoStub = sinon.stub();

t.context.cache = await esmock.p("../../../../lib/cli/commands/cache.js", {
"@ui5/project/config/Configuration": {
default: {
fromFile: t.context.configurationFromFileStub,
},
},
"@ui5/project/internal/ui5Framework/cache": {
default: class {
static getCacheInfo = t.context.frameworkCacheGetCacheInfo;
Expand All @@ -86,6 +79,10 @@ test.beforeEach(async (t) => {
"yesno": {
default: t.context.yesnoStub,
},
}, {
"../../../../lib/dataDir.js": {
getUi5DataDirOrDefault: t.context.getUi5DataDirOrDefaultStub
}
});
});

Expand Down Expand Up @@ -137,9 +134,9 @@ test.serial("Command definition is correct", (t) => {

// ─── ui5DataDir resolution ──────────────────────────────────────────────────

test.serial("ui5 cache clean: uses resolved path from configuration", async (t) => {
test.serial("ui5 cache clean: uses resolved path from getUi5DataDirOrDefault", async (t) => {
const {cache, argv, frameworkCacheGetCacheInfo, buildCacheGetCacheInfo,
stderrWriteStub, configurationFromFileStub, configurationGetUi5DataDirStub} = t.context;
stderrWriteStub, getUi5DataDirOrDefaultStub} = t.context;

frameworkCacheGetCacheInfo.resolves(null);
buildCacheGetCacheInfo.resolves(null);
Expand All @@ -148,40 +145,21 @@ test.serial("ui5 cache clean: uses resolved path from configuration", async (t)
setLogLevel("verbose");
await cache.handler(argv);

t.is(configurationFromFileStub.callCount, 1, "Configuration.fromFile called exactly once");
t.is(configurationGetUi5DataDirStub.callCount, 1, "Configuration#getUi5DataDir called exactly once");
t.is(getUi5DataDirOrDefaultStub.callCount, 1, "getUi5DataDirOrDefault called exactly once");

t.is(frameworkCacheGetCacheInfo.firstCall.args[0], TEST_UI5_DATA_DIR,
"getCacheInfo receives the path returned by configuration");
"getCacheInfo receives the resolved path");

const allOutput = stderrWriteStub.args.map((a) => a[0]).join("");
t.true(allOutput.includes(TEST_UI5_DATA_DIR), "Resolved ui5DataDir shown in checking line");
});

test.serial("ui5 cache clean: prefers UI5_DATA_DIR env var over configuration", async (t) => {
const {cache, argv, frameworkCacheGetCacheInfo, buildCacheGetCacheInfo,
configurationFromFileStub} = t.context;

const envUi5DataDir = path.resolve("env-ui5-home");
process.env.UI5_DATA_DIR = envUi5DataDir;
frameworkCacheGetCacheInfo.resolves(null);
buildCacheGetCacheInfo.resolves(null);

argv["_"] = ["cache", "clean"];
await cache.handler(argv);

t.is(configurationFromFileStub.callCount, 0,
"Configuration.fromFile must not be called when UI5_DATA_DIR is set");
t.is(frameworkCacheGetCacheInfo.firstCall.args[0], envUi5DataDir,
"getCacheInfo receives value from UI5_DATA_DIR");
});

test.serial("ui5 cache clean: falls back to ~/.ui5 when configuration has no value", async (t) => {
test.serial("ui5 cache clean: uses ~/.ui5 fallback provided by getUi5DataDirOrDefault", async (t) => {
const {cache, argv, frameworkCacheGetCacheInfo, buildCacheGetCacheInfo,
stderrWriteStub, configurationGetUi5DataDirStub} = t.context;
stderrWriteStub, getUi5DataDirOrDefaultStub} = t.context;

const fallbackUi5DataDir = path.join(os.homedir(), ".ui5");
configurationGetUi5DataDirStub.returns(undefined);
getUi5DataDirOrDefaultStub.resolves(fallbackUi5DataDir);
frameworkCacheGetCacheInfo.resolves(null);
buildCacheGetCacheInfo.resolves(null);

Expand All @@ -193,7 +171,10 @@ test.serial("ui5 cache clean: falls back to ~/.ui5 when configuration has no val
"getCacheInfo receives default ~/.ui5 path when no configured value exists");

const allOutput = stderrWriteStub.args.map((a) => a[0]).join("");
t.true(allOutput.includes(fallbackUi5DataDir), "Fallback ui5DataDir shown in checking line");
const shortenedDataDir = "~" + path.sep + ".ui5";
t.true(allOutput.includes(shortenedDataDir),
"Fallback ui5DataDir shown with ~ in checking line");
t.false(allOutput.includes(os.homedir()), "Full home directory is not printed");
});

// ─── Basic flow ─────────────────────────────────────────────────────────────
Expand Down
Loading
Loading