Skip to content

Commit e6cf8af

Browse files
feat: remove wsl specific npm dependencies
1 parent 841a330 commit e6cf8af

File tree

8 files changed

+392
-352
lines changed

8 files changed

+392
-352
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,10 @@
22

33
All notable changes to this project will be documented in this file.
44

5+
## [1.51.0] (08/01/2026)
6+
7+
Replace unnecessary external Windows-specific dependencies with built-in functionality to improve cross-platform compatibility and reduce risk of dependency-related issues. This update should not have any user impact.
8+
59
## [1.50.0] (07/01/2026)
610

711
We introduce a new command `silverfin generate-export-file` which enables the creation of export files (XBRLs, iXBRLs, CSV, etc.) with the CLI. This could be used as part of your development process, for example, after updating an export file template to quickly generate a new export without the need to go to Silverfin's website. It should display any validation errors in the terminal and open the generated file in the default application (browser, text editor, etc.). See more details on how to use it by running `silverfin generate-export-file --help`.

lib/exportFileInstanceGenerator.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,7 @@ class ExportFileInstanceGenerator {
8686
}
8787

8888
#logValidationErrors(response) {
89-
if (response && response.validation_errors) {
89+
if (response && response.validation_errors && response.validation_errors.length > 0) {
9090
consola.warn(`Validation errors: ${response.validation_errors}`);
9191
}
9292
}

lib/liquidTestRunner.js

Lines changed: 7 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,4 @@
11
const yaml = require("yaml");
2-
const axios = require("axios");
3-
const open = require("open");
4-
const path = require("path");
5-
const { exec, execSync } = require("child_process");
6-
const isWsl = require("is-wsl");
7-
const commandExistsSync = require("command-exists").sync;
82
const fs = require("fs");
93
const chalk = require("chalk");
104
const errorUtils = require("./utils/errorUtils");
@@ -13,6 +7,7 @@ const SF = require("./api/sfApi");
137
const fsUtils = require("./utils/fsUtils");
148
const runTestUtils = require("./utils/runTestUtils");
159
const { consola } = require("consola");
10+
const { UrlHandler } = require("./utils/urlHandler");
1611

1712
const { ReconciliationText } = require("./templates/reconciliationText");
1813
const { AccountTemplate } = require("./templates/accountTemplate");
@@ -147,9 +142,7 @@ function buildTestParams(firmId, templateType, handle, testName = "", renderMode
147142

148143
finalTests = filteredContent;
149144
lineAdjustments = patternLineAdjustments;
150-
consola.info(
151-
`Running ${matchingTests.length} test${matchingTests.length === 1 ? "" : "s"} matching pattern "${pattern}":`
152-
);
145+
consola.info(`Running ${matchingTests.length} test${matchingTests.length === 1 ? "" : "s"} matching pattern "${pattern}":`);
153146
matchingTests.forEach((testName) => {
154147
consola.log(` • ${testName}`);
155148
});
@@ -384,53 +377,11 @@ function processTestRunResponse(testRun, previewOnly, lineAdjustments = {}) {
384377
}
385378
}
386379

387-
// Path to store HTML exports
388-
function resolveHTMLPath(fileName) {
389-
const homedir = require("os").homedir();
390-
const folderPath = path.resolve(homedir, ".silverfin/html_exports");
391-
const filePath = path.resolve(folderPath, `${fileName}.html`);
392-
fsUtils.createFolder(folderPath);
393-
return filePath;
394-
}
395-
396-
// Retrieve HTML, store it and open it in the default browser if needed
380+
// Retrieve HTML and open it in the default browser if needed
397381
async function getHTML(url, testName, openBrowser = false, htmlMode) {
398-
const filePath = resolveHTMLPath(`${testName}_${htmlMode}`);
399-
const htmlResponse = await axios.get(url);
400-
if (htmlResponse.status === 200) {
401-
fs.writeFileSync(filePath, htmlResponse.data);
402-
if (openBrowser) {
403-
if (isWsl) {
404-
if (commandExistsSync("wsl-open")) {
405-
exec(`wsl-open ${filePath}`);
406-
} else {
407-
consola.info("In order to automatically open HTML files on WSL, we need to install the wsl-open script.");
408-
consola.log("You might be prompted for your password in order for us to install 'sudo npm install -g wsl-open'");
409-
execSync("sudo npm install -g wsl-open");
410-
consola.log("Installed wsl-open script");
411-
exec(`wsl-open ${filePath}`);
412-
}
413-
} else {
414-
await open(filePath);
415-
}
416-
}
417-
}
418-
}
419-
420-
async function deleteExistingHTMLs() {
421-
try {
422-
const homedir = require("os").homedir();
423-
const folderPath = path.resolve(homedir, ".silverfin/html_exports");
424-
if (!fs.existsSync(folderPath)) {
425-
return;
426-
}
427-
const files = fs.readdirSync(folderPath);
428-
files.forEach((fileName) => {
429-
const filePath = path.resolve(folderPath, fileName);
430-
fs.unlinkSync(filePath);
431-
});
432-
} catch (err) {
433-
consola.debug(`Error while deleting existing HTML files`);
382+
if (openBrowser) {
383+
const filename = `${testName}_${htmlMode}`;
384+
await new UrlHandler(url, filename).openFile();
434385
}
435386
}
436387

@@ -447,7 +398,6 @@ async function getHTMLrenders(renderMode, testName, testRun, openBrowser) {
447398
}
448399

449400
async function handleHTMLfiles(testName = "", testRun, renderMode) {
450-
deleteExistingHTMLs();
451401
if (testName) {
452402
// Only one test
453403
getHTMLrenders(renderMode, testName, testRun, true);
@@ -496,16 +446,7 @@ async function runTests(firmId, templateType, handle, testName = "", previewOnly
496446
}
497447
}
498448

499-
async function runTestsWithOutput(
500-
firmId,
501-
templateType,
502-
handle,
503-
testName = "",
504-
previewOnly = false,
505-
htmlInput = false,
506-
htmlPreview = false,
507-
pattern = ""
508-
) {
449+
async function runTestsWithOutput(firmId, templateType, handle, testName = "", previewOnly = false, htmlInput = false, htmlPreview = false, pattern = "") {
509450
try {
510451
if (templateType !== "reconciliationText" && templateType !== "accountTemplate") {
511452
consola.error(`Template type is missing or invalid`);
@@ -562,6 +503,5 @@ module.exports = {
562503
runTestsWithOutput,
563504
runTestsStatusOnly,
564505
getHTML,
565-
resolveHTMLPath,
566506
checkAllTestsErrorsPresent,
567507
};

lib/utils/urlHandler.js

Lines changed: 54 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,16 +4,18 @@ const axios = require("axios");
44
const { consola } = require("consola");
55
const errorUtils = require("./errorUtils");
66
const fs = require("fs");
7+
const { WSLHandler } = require("./wslHandler");
78

89
/**
910
* Class to handle URL operations such as downloading and opening files.
1011
*/
1112
class UrlHandler {
12-
constructor(url) {
13+
constructor(url, customFilename = null) {
1314
if (!url) {
1415
throw new Error("The 'url' parameter is required.");
1516
}
1617
this.url = url;
18+
this.customFilename = customFilename;
1719
}
1820

1921
/** Opens the file from the URL in the default application.
@@ -22,19 +24,41 @@ class UrlHandler {
2224
async openFile() {
2325
try {
2426
const filePath = await this.#downloadFile();
25-
await open(filePath);
27+
await this.#openLocalFile(filePath);
2628
} catch (error) {
2729
consola.error(`Failed to open URL: ${this.url}`, error);
2830
}
2931
}
3032

33+
async #openLocalFile(filePath) {
34+
if (WSLHandler.isWSL()) {
35+
await WSLHandler.open(filePath);
36+
} else {
37+
await open(filePath);
38+
}
39+
}
40+
3141
async #downloadFile() {
3242
try {
3343
const response = await axios.get(this.url, { responseType: "arraybuffer" });
3444
const contentDisposition = response.headers["content-disposition"];
35-
const fileExtension = contentDisposition ? this.#identifyExtension(contentDisposition) : "";
36-
const tempFilePath = path.resolve(require("os").tmpdir(), "silverfin", `${Date.now()}.${fileExtension}`);
37-
fs.mkdirSync(path.dirname(tempFilePath), { recursive: true });
45+
46+
let filename;
47+
const fileExtension = contentDisposition ? this.#identifyFileExtension(contentDisposition) : "html";
48+
49+
if (this.customFilename) {
50+
// Use custom filename with inferred extension
51+
filename = `${this.customFilename}.${fileExtension}`;
52+
} else {
53+
// Try to infer filename from response, fall back to timestamp
54+
const inferredFilename = contentDisposition ? this.#identifyFilename(contentDisposition) : null;
55+
filename = inferredFilename || `${Date.now()}.${fileExtension}`;
56+
}
57+
58+
const tempDir = path.resolve(require("os").tmpdir(), "silverfin");
59+
fs.mkdirSync(tempDir, { recursive: true });
60+
61+
const tempFilePath = this.#getUniqueFilePath(tempDir, filename);
3862
fs.writeFileSync(tempFilePath, response.data);
3963

4064
return tempFilePath;
@@ -43,7 +67,31 @@ class UrlHandler {
4367
}
4468
}
4569

46-
#identifyExtension(string) {
70+
#getUniqueFilePath(directory, filename) {
71+
const ext = path.extname(filename);
72+
const nameWithoutExt = path.basename(filename, ext);
73+
let filePath = path.resolve(directory, filename);
74+
let counter = 1;
75+
76+
// If file exists, append (1), (2), etc. until we find a unique name
77+
while (fs.existsSync(filePath)) {
78+
const uniqueFilename = `${nameWithoutExt} (${counter})${ext}`;
79+
filePath = path.resolve(directory, uniqueFilename);
80+
counter++;
81+
}
82+
83+
return filePath;
84+
}
85+
86+
#identifyFilename(string) {
87+
if (!string) return null;
88+
// Match full filename from content-disposition header
89+
// Handles: filename="file.ext", filename*=UTF-8''file.ext, filename=file.ext
90+
const match = string.match(/filename[*]?=['"]?(?:UTF-8'')?([^'";\s]+)['";\s]?/i);
91+
return match ? decodeURIComponent(match[1]) : null;
92+
}
93+
94+
#identifyFileExtension(string) {
4795
if (!string) return null;
4896
const match = string.match(/filename[*]?=['"]?(?:[^'"]*\.)?([^.'";\s]+)['"]/i);
4997
return match ? match[1] : null;

lib/utils/wslHandler.js

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
const { execFile, execSync } = require("child_process");
2+
const { promisify } = require("util");
3+
const fs = require("fs");
4+
const { consola } = require("consola");
5+
const prompt = require("prompt-sync")({ sigint: true });
6+
7+
const execFileAsync = promisify(execFile);
8+
9+
class WSLHandler {
10+
/**
11+
* Detects if running in Windows Subsystem for Linux (WSL)
12+
* @returns {boolean} true if running in WSL, false otherwise
13+
*/
14+
static isWSL() {
15+
try {
16+
const version = fs.readFileSync("/proc/version", "utf8").toLowerCase();
17+
return version.includes("microsoft") || version.includes("wsl");
18+
} catch {
19+
return false;
20+
}
21+
}
22+
23+
static async open(filePath) {
24+
this.#setupWslOpen();
25+
26+
try {
27+
await execFileAsync("wsl-open", [filePath]);
28+
} catch (error) {
29+
consola.error(`Failed to open file in WSL: ${filePath}`, error);
30+
}
31+
}
32+
33+
static #wslOpenInPath() {
34+
try {
35+
execSync(`which wsl-open`, { stdio: "ignore" });
36+
return true;
37+
} catch {
38+
consola.log(`Command 'wsl-open' not found in PATH.`);
39+
return false;
40+
}
41+
}
42+
43+
static #setupWslOpen() {
44+
if (this.#wslOpenInPath()) {
45+
return;
46+
}
47+
48+
consola.info("In order to automatically open files on WSL, we need to install the wsl-open script.");
49+
consola.log("You might be prompted for your password in order for us to install 'sudo npm install -g wsl-open'");
50+
51+
const response = prompt("Do you want to proceed? (y/N): ");
52+
if (response?.toLowerCase() !== "y") {
53+
consola.warn("Skipping wsl-open installation. Files will not open automatically in WSL.");
54+
return;
55+
}
56+
57+
execSync("sudo npm install -g wsl-open");
58+
consola.log("Installed wsl-open script");
59+
}
60+
}
61+
62+
module.exports = { WSLHandler };

package-lock.json

Lines changed: 2 additions & 10 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "silverfin-cli",
3-
"version": "1.50.0",
3+
"version": "1.51.0",
44
"description": "Command line tool for Silverfin template development",
55
"main": "index.js",
66
"license": "MIT",
@@ -25,10 +25,8 @@
2525
"axios": "^1.6.2",
2626
"chalk": "4.1.2",
2727
"chokidar": "^3.6.0",
28-
"command-exists": "^1.2.9",
2928
"commander": "^9.4.0",
3029
"consola": "^3.2.3",
31-
"is-wsl": "^2.2.0",
3230
"open": "^8.4.0",
3331
"prompt-sync": "^4.2.0",
3432
"yaml": "^2.2.1"

0 commit comments

Comments
 (0)