Expand TILDE (~) in path inputs (#160)

Closes: #159
This commit is contained in:
Kevin Stillhammer 2024-11-23 09:21:51 +01:00 committed by GitHub
parent 7c238111e6
commit caf0cab7a6
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 410 additions and 21 deletions

View File

@ -121,3 +121,49 @@ jobs:
CACHE_HIT: ${{ steps.restore.outputs.cache-hit }}
- run: uv sync
working-directory: __tests__/fixtures/uv-project
prepare-tilde-expansion-tests:
runs-on: selfhosted-ubuntu-arm64
steps:
- name: Create cache directory
run: mkdir -p ~/uv-cache
shell: bash
- name: Create cache dependency glob file
run: touch ~/uv-cache.glob
shell: bash
test-tilde-expansion-cache-local-path:
needs: prepare-tilde-expansion-tests
runs-on: selfhosted-ubuntu-arm64
steps:
- uses: actions/checkout@v4
- name: Setup with cache
uses: ./
with:
enable-cache: true
cache-local-path: ~/uv-cache/cache-local-path
test-tilde-expansion-cache-dependency-glob:
needs: prepare-tilde-expansion-tests
runs-on: selfhosted-ubuntu-arm64
steps:
- uses: actions/checkout@v4
- name: Setup with cache
uses: ./
with:
enable-cache: true
cache-local-path: ~/uv-cache/cache-dependency-glob
cache-dependency-glob: "~/uv-cache.glob"
cleanup-tilde-expansion-tests:
needs:
- test-tilde-expansion-cache-local-path
- test-tilde-expansion-cache-dependency-glob
runs-on: selfhosted-ubuntu-arm64
steps:
- name: Remove cache directory
run: rm -rf ~/uv-cache
shell: bash
- name: Remove cache dependency glob file
run: rm -f ~/uv-cache.glob
shell: bash

View File

@ -123,3 +123,24 @@ jobs:
uses: ./
- run: uv tool install ruff
- run: ruff --version
test-tilde-expansion-tool-dirs:
runs-on: selfhosted-ubuntu-arm64
steps:
- uses: actions/checkout@v4
- name: Setup with cache
uses: ./
with:
tool-bin-dir: "~/tool-bin-dir"
tool-dir: "~/tool-dir"
- name: "Check if tool dirs are expanded"
run: |
if ! echo "$PATH" | grep -q "/home/ubuntu/tool-bin-dir"; then
echo "PATH does not contain /home/ubuntu/tool-bin-dir: $PATH"
exit 1
fi
if [ "$UV_TOOL_DIR" != "/home/ubuntu/tool-dir" ]; then
echo "UV_TOOL_DIR does not contain /home/ubuntu/tool-dir: $UV_TOOL_DIR"
exit 1
fi

View File

@ -21,6 +21,7 @@ Set up your GitHub Actions workflow with a specific version of [uv](https://docs
- [GitHub authentication token](#github-authentication-token)
- [UV_TOOL_DIR](#uv_tool_dir)
- [UV_TOOL_BIN_DIR](#uv_tool_bin_dir)
- [Tilde Expansion](#tilde-expansion)
- [How it works](#how-it-works)
- [FAQ](#faq)
@ -120,7 +121,7 @@ use it in subsequent steps. For example, to use the cache in the above case:
If you want to control when the cache is invalidated, specify a glob pattern with the
`cache-dependency-glob` input. The cache will be invalidated if any file matching the glob pattern
changes. The glob matches files relative to the repository root.
changes. If you use relative paths, the glob matches files relative to the repository root.
> [!NOTE]
>
@ -144,6 +145,14 @@ changes. The glob matches files relative to the repository root.
**/pyproject.toml
```
```yaml
- name: Define an absolute cache dependency glob
uses: astral-sh/setup-uv@v3
with:
enable-cache: true
cache-dependency-glob: "/tmp/my-folder/requirements*.txt"
```
```yaml
- name: Never invalidate the cache
uses: astral-sh/setup-uv@v3
@ -240,6 +249,25 @@ If you want to change this behaviour (especially on self-hosted runners) you can
tool-bin-dir: "/path/to/tool-bin/dir"
```
### Tilde Expansion
This action supports expanding the `~` character to the user's home directory for the following inputs:
- `cache-local-path`
- `tool-dir`
- `tool-bin-dir`
- `cache-dependency-glob`
```yaml
- name: Expand the tilde character
uses: astral-sh/setup-uv@v3
with:
cache-local-path: "~/path/to/cache"
tool-dir: "~/path/to/tool/dir"
tool-bin-dir: "~/path/to/tool-bin/dir"
cache-dependency-glob: "~/my-cache-buster"
```
## How it works
This action downloads uv from the uv repo's official

140
dist/save-cache/index.js generated vendored
View File

@ -82304,10 +82304,10 @@ Object.defineProperty(exports, "__esModule", ({ value: true }));
exports.STATE_CACHE_MATCHED_KEY = exports.STATE_CACHE_KEY = void 0;
exports.restoreCache = restoreCache;
const cache = __importStar(__nccwpck_require__(5116));
const glob = __importStar(__nccwpck_require__(7206));
const core = __importStar(__nccwpck_require__(7484));
const inputs_1 = __nccwpck_require__(9612);
const platforms_1 = __nccwpck_require__(8361);
const hash_files_1 = __nccwpck_require__(9660);
exports.STATE_CACHE_KEY = "cache-key";
exports.STATE_CACHE_MATCHED_KEY = "cache-matched-key";
const CACHE_VERSION = "1";
@ -82334,7 +82334,7 @@ function computeKeys(version) {
let cacheDependencyPathHash = "-";
if (inputs_1.cacheDependencyGlob !== "") {
core.info(`Searching files using cache dependency glob: ${inputs_1.cacheDependencyGlob.split("\n").join(",")}`);
cacheDependencyPathHash += yield glob.hashFiles(inputs_1.cacheDependencyGlob, undefined, undefined, true);
cacheDependencyPathHash += yield (0, hash_files_1.hashFiles)(inputs_1.cacheDependencyGlob, true);
if (cacheDependencyPathHash === "-") {
throw new Error(`No file in ${process.cwd()} matched to [${inputs_1.cacheDependencyGlob.split("\n").join(",")}], make sure you have checked out the target repository`);
}
@ -82358,6 +82358,114 @@ function handleMatchResult(matchedKey, primaryKey) {
}
/***/ }),
/***/ 9660:
/***/ (function(__unused_webpack_module, exports, __nccwpck_require__) {
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
var __asyncValues = (this && this.__asyncValues) || function (o) {
if (!Symbol.asyncIterator) throw new TypeError("Symbol.asyncIterator is not defined.");
var m = o[Symbol.asyncIterator], i;
return m ? m.call(o) : (o = typeof __values === "function" ? __values(o) : o[Symbol.iterator](), i = {}, verb("next"), verb("throw"), verb("return"), i[Symbol.asyncIterator] = function () { return this; }, i);
function verb(n) { i[n] = o[n] && function (v) { return new Promise(function (resolve, reject) { v = o[n](v), settle(resolve, reject, v.done, v.value); }); }; }
function settle(resolve, reject, d, v) { Promise.resolve(v).then(function(v) { resolve({ value: v, done: d }); }, reject); }
};
Object.defineProperty(exports, "__esModule", ({ value: true }));
exports.hashFiles = hashFiles;
const crypto = __importStar(__nccwpck_require__(7598));
const core = __importStar(__nccwpck_require__(7484));
const fs = __importStar(__nccwpck_require__(3024));
const stream = __importStar(__nccwpck_require__(7075));
const util = __importStar(__nccwpck_require__(7975));
const glob_1 = __nccwpck_require__(7206);
/**
* Hashes files matching the given glob pattern.
*
* Copied from https://github.com/actions/toolkit/blob/20ed2908f19538e9dfb66d8083f1171c0a50a87c/packages/glob/src/internal-hash-files.ts#L9-L49
* But supports hashing files outside the GITHUB_WORKSPACE.
* @param pattern The glob pattern to match files.
* @param verbose Whether to log the files being hashed.
*/
function hashFiles(pattern_1) {
return __awaiter(this, arguments, void 0, function* (pattern, verbose = false) {
var _a, e_1, _b, _c;
const globber = yield (0, glob_1.create)(pattern);
let hasMatch = false;
const writeDelegate = verbose ? core.info : core.debug;
const result = crypto.createHash("sha256");
let count = 0;
try {
for (var _d = true, _e = __asyncValues(globber.globGenerator()), _f; _f = yield _e.next(), _a = _f.done, !_a; _d = true) {
_c = _f.value;
_d = false;
const file = _c;
writeDelegate(file);
if (fs.statSync(file).isDirectory()) {
writeDelegate(`Skip directory '${file}'.`);
continue;
}
const hash = crypto.createHash("sha256");
const pipeline = util.promisify(stream.pipeline);
yield pipeline(fs.createReadStream(file), hash);
result.write(hash.digest());
count++;
if (!hasMatch) {
hasMatch = true;
}
}
}
catch (e_1_1) { e_1 = { error: e_1_1 }; }
finally {
try {
if (!_d && !_a && (_b = _e.return)) yield _b.call(_e);
}
finally { if (e_1) throw e_1.error; }
}
result.end();
if (hasMatch) {
writeDelegate(`Found ${count} files to hash.`);
return result.digest("hex");
}
writeDelegate("No matches found for glob");
return "";
});
}
/***/ }),
/***/ 1653:
@ -82501,7 +82609,7 @@ exports.githubToken = core.getInput("github-token");
function getToolBinDir() {
const toolBinDirInput = core.getInput("tool-bin-dir");
if (toolBinDirInput !== "") {
return toolBinDirInput;
return expandTilde(toolBinDirInput);
}
if (process.platform === "win32") {
if (process.env.RUNNER_TEMP !== undefined) {
@ -82514,7 +82622,7 @@ function getToolBinDir() {
function getToolDir() {
const toolDirInput = core.getInput("tool-dir");
if (toolDirInput !== "") {
return toolDirInput;
return expandTilde(toolDirInput);
}
if (process.platform === "win32") {
if (process.env.RUNNER_TEMP !== undefined) {
@ -82527,13 +82635,19 @@ function getToolDir() {
function getCacheLocalPath() {
const cacheLocalPathInput = core.getInput("cache-local-path");
if (cacheLocalPathInput !== "") {
return cacheLocalPathInput;
return expandTilde(cacheLocalPathInput);
}
if (process.env.RUNNER_TEMP !== undefined) {
return `${process.env.RUNNER_TEMP}${node_path_1.default.sep}setup-uv-cache`;
}
throw Error("Could not determine UV_CACHE_DIR. Please make sure RUNNER_TEMP is set or provide the cache-local-path input");
}
function expandTilde(input) {
if (input.startsWith("~")) {
return `${process.env.HOME}${input.substring(1)}`;
}
return input;
}
/***/ }),
@ -82684,6 +82798,14 @@ module.exports = require("net");
/***/ }),
/***/ 7598:
/***/ ((module) => {
"use strict";
module.exports = require("node:crypto");
/***/ }),
/***/ 8474:
/***/ ((module) => {
@ -82692,6 +82814,14 @@ module.exports = require("node:events");
/***/ }),
/***/ 3024:
/***/ ((module) => {
"use strict";
module.exports = require("node:fs");
/***/ }),
/***/ 6760:
/***/ ((module) => {

124
dist/setup/index.js generated vendored
View File

@ -87387,10 +87387,10 @@ Object.defineProperty(exports, "__esModule", ({ value: true }));
exports.STATE_CACHE_MATCHED_KEY = exports.STATE_CACHE_KEY = void 0;
exports.restoreCache = restoreCache;
const cache = __importStar(__nccwpck_require__(5116));
const glob = __importStar(__nccwpck_require__(7206));
const core = __importStar(__nccwpck_require__(7484));
const inputs_1 = __nccwpck_require__(9612);
const platforms_1 = __nccwpck_require__(8361);
const hash_files_1 = __nccwpck_require__(9660);
exports.STATE_CACHE_KEY = "cache-key";
exports.STATE_CACHE_MATCHED_KEY = "cache-matched-key";
const CACHE_VERSION = "1";
@ -87417,7 +87417,7 @@ function computeKeys(version) {
let cacheDependencyPathHash = "-";
if (inputs_1.cacheDependencyGlob !== "") {
core.info(`Searching files using cache dependency glob: ${inputs_1.cacheDependencyGlob.split("\n").join(",")}`);
cacheDependencyPathHash += yield glob.hashFiles(inputs_1.cacheDependencyGlob, undefined, undefined, true);
cacheDependencyPathHash += yield (0, hash_files_1.hashFiles)(inputs_1.cacheDependencyGlob, true);
if (cacheDependencyPathHash === "-") {
throw new Error(`No file in ${process.cwd()} matched to [${inputs_1.cacheDependencyGlob.split("\n").join(",")}], make sure you have checked out the target repository`);
}
@ -89968,6 +89968,114 @@ function getAvailableVersions(githubToken) {
}
/***/ }),
/***/ 9660:
/***/ (function(__unused_webpack_module, exports, __nccwpck_require__) {
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
var __asyncValues = (this && this.__asyncValues) || function (o) {
if (!Symbol.asyncIterator) throw new TypeError("Symbol.asyncIterator is not defined.");
var m = o[Symbol.asyncIterator], i;
return m ? m.call(o) : (o = typeof __values === "function" ? __values(o) : o[Symbol.iterator](), i = {}, verb("next"), verb("throw"), verb("return"), i[Symbol.asyncIterator] = function () { return this; }, i);
function verb(n) { i[n] = o[n] && function (v) { return new Promise(function (resolve, reject) { v = o[n](v), settle(resolve, reject, v.done, v.value); }); }; }
function settle(resolve, reject, d, v) { Promise.resolve(v).then(function(v) { resolve({ value: v, done: d }); }, reject); }
};
Object.defineProperty(exports, "__esModule", ({ value: true }));
exports.hashFiles = hashFiles;
const crypto = __importStar(__nccwpck_require__(7598));
const core = __importStar(__nccwpck_require__(7484));
const fs = __importStar(__nccwpck_require__(3024));
const stream = __importStar(__nccwpck_require__(7075));
const util = __importStar(__nccwpck_require__(7975));
const glob_1 = __nccwpck_require__(7206);
/**
* Hashes files matching the given glob pattern.
*
* Copied from https://github.com/actions/toolkit/blob/20ed2908f19538e9dfb66d8083f1171c0a50a87c/packages/glob/src/internal-hash-files.ts#L9-L49
* But supports hashing files outside the GITHUB_WORKSPACE.
* @param pattern The glob pattern to match files.
* @param verbose Whether to log the files being hashed.
*/
function hashFiles(pattern_1) {
return __awaiter(this, arguments, void 0, function* (pattern, verbose = false) {
var _a, e_1, _b, _c;
const globber = yield (0, glob_1.create)(pattern);
let hasMatch = false;
const writeDelegate = verbose ? core.info : core.debug;
const result = crypto.createHash("sha256");
let count = 0;
try {
for (var _d = true, _e = __asyncValues(globber.globGenerator()), _f; _f = yield _e.next(), _a = _f.done, !_a; _d = true) {
_c = _f.value;
_d = false;
const file = _c;
writeDelegate(file);
if (fs.statSync(file).isDirectory()) {
writeDelegate(`Skip directory '${file}'.`);
continue;
}
const hash = crypto.createHash("sha256");
const pipeline = util.promisify(stream.pipeline);
yield pipeline(fs.createReadStream(file), hash);
result.write(hash.digest());
count++;
if (!hasMatch) {
hasMatch = true;
}
}
}
catch (e_1_1) { e_1 = { error: e_1_1 }; }
finally {
try {
if (!_d && !_a && (_b = _e.return)) yield _b.call(_e);
}
finally { if (e_1) throw e_1.error; }
}
result.end();
if (hasMatch) {
writeDelegate(`Found ${count} files to hash.`);
return result.digest("hex");
}
writeDelegate("No matches found for glob");
return "";
});
}
/***/ }),
/***/ 2180:
@ -90176,7 +90284,7 @@ exports.githubToken = core.getInput("github-token");
function getToolBinDir() {
const toolBinDirInput = core.getInput("tool-bin-dir");
if (toolBinDirInput !== "") {
return toolBinDirInput;
return expandTilde(toolBinDirInput);
}
if (process.platform === "win32") {
if (process.env.RUNNER_TEMP !== undefined) {
@ -90189,7 +90297,7 @@ function getToolBinDir() {
function getToolDir() {
const toolDirInput = core.getInput("tool-dir");
if (toolDirInput !== "") {
return toolDirInput;
return expandTilde(toolDirInput);
}
if (process.platform === "win32") {
if (process.env.RUNNER_TEMP !== undefined) {
@ -90202,13 +90310,19 @@ function getToolDir() {
function getCacheLocalPath() {
const cacheLocalPathInput = core.getInput("cache-local-path");
if (cacheLocalPathInput !== "") {
return cacheLocalPathInput;
return expandTilde(cacheLocalPathInput);
}
if (process.env.RUNNER_TEMP !== undefined) {
return `${process.env.RUNNER_TEMP}${node_path_1.default.sep}setup-uv-cache`;
}
throw Error("Could not determine UV_CACHE_DIR. Please make sure RUNNER_TEMP is set or provide the cache-local-path input");
}
function expandTilde(input) {
if (input.startsWith("~")) {
return `${process.env.HOME}${input.substring(1)}`;
}
return input;
}
/***/ }),

View File

@ -1,5 +1,4 @@
import * as cache from "@actions/cache";
import * as glob from "@actions/glob";
import * as core from "@actions/core";
import {
cacheDependencyGlob,
@ -7,6 +6,7 @@ import {
cacheSuffix,
} from "../utils/inputs";
import { getArch, getPlatform } from "../utils/platforms";
import { hashFiles } from "../hash/hash-files";
export const STATE_CACHE_KEY = "cache-key";
export const STATE_CACHE_MATCHED_KEY = "cache-matched-key";
@ -39,12 +39,7 @@ async function computeKeys(version: string): Promise<string> {
core.info(
`Searching files using cache dependency glob: ${cacheDependencyGlob.split("\n").join(",")}`,
);
cacheDependencyPathHash += await glob.hashFiles(
cacheDependencyGlob,
undefined,
undefined,
true,
);
cacheDependencyPathHash += await hashFiles(cacheDependencyGlob, true);
if (cacheDependencyPathHash === "-") {
throw new Error(
`No file in ${process.cwd()} matched to [${cacheDependencyGlob.split("\n").join(",")}], make sure you have checked out the target repository`,

48
src/hash/hash-files.ts Normal file
View File

@ -0,0 +1,48 @@
import * as crypto from "node:crypto";
import * as core from "@actions/core";
import * as fs from "node:fs";
import * as stream from "node:stream";
import * as util from "node:util";
import { create } from "@actions/glob";
/**
* Hashes files matching the given glob pattern.
*
* Copied from https://github.com/actions/toolkit/blob/20ed2908f19538e9dfb66d8083f1171c0a50a87c/packages/glob/src/internal-hash-files.ts#L9-L49
* But supports hashing files outside the GITHUB_WORKSPACE.
* @param pattern The glob pattern to match files.
* @param verbose Whether to log the files being hashed.
*/
export async function hashFiles(
pattern: string,
verbose = false,
): Promise<string> {
const globber = await create(pattern);
let hasMatch = false;
const writeDelegate = verbose ? core.info : core.debug;
const result = crypto.createHash("sha256");
let count = 0;
for await (const file of globber.globGenerator()) {
writeDelegate(file);
if (fs.statSync(file).isDirectory()) {
writeDelegate(`Skip directory '${file}'.`);
continue;
}
const hash = crypto.createHash("sha256");
const pipeline = util.promisify(stream.pipeline);
await pipeline(fs.createReadStream(file), hash);
result.write(hash.digest());
count++;
if (!hasMatch) {
hasMatch = true;
}
}
result.end();
if (hasMatch) {
writeDelegate(`Found ${count} files to hash.`);
return result.digest("hex");
}
writeDelegate("No matches found for glob");
return "";
}

View File

@ -15,7 +15,7 @@ export const githubToken = core.getInput("github-token");
function getToolBinDir(): string | undefined {
const toolBinDirInput = core.getInput("tool-bin-dir");
if (toolBinDirInput !== "") {
return toolBinDirInput;
return expandTilde(toolBinDirInput);
}
if (process.platform === "win32") {
if (process.env.RUNNER_TEMP !== undefined) {
@ -31,7 +31,7 @@ function getToolBinDir(): string | undefined {
function getToolDir(): string | undefined {
const toolDirInput = core.getInput("tool-dir");
if (toolDirInput !== "") {
return toolDirInput;
return expandTilde(toolDirInput);
}
if (process.platform === "win32") {
if (process.env.RUNNER_TEMP !== undefined) {
@ -47,7 +47,7 @@ function getToolDir(): string | undefined {
function getCacheLocalPath(): string {
const cacheLocalPathInput = core.getInput("cache-local-path");
if (cacheLocalPathInput !== "") {
return cacheLocalPathInput;
return expandTilde(cacheLocalPathInput);
}
if (process.env.RUNNER_TEMP !== undefined) {
return `${process.env.RUNNER_TEMP}${path.sep}setup-uv-cache`;
@ -56,3 +56,10 @@ function getCacheLocalPath(): string {
"Could not determine UV_CACHE_DIR. Please make sure RUNNER_TEMP is set or provide the cache-local-path input",
);
}
function expandTilde(input: string): string {
if (input.startsWith("~")) {
return `${process.env.HOME}${input.substring(1)}`;
}
return input;
}