From 2a009b38b9d140880c7c88733994789138e5712c Mon Sep 17 00:00:00 2001
From: ace-n-msft <acenassri@microsoft.com>
Date: Fri, 27 Dec 2024 16:13:55 -0800
Subject: [PATCH] Feat: allow configuration via JS files

---
 spec/e2e/pbivizConfigSpec.js | 66 ++++++++++++++++++++++++++++++++++++
 spec/e2e/testUtils.js        | 14 ++++++++
 src/utils.ts                 | 22 +++++++++---
 3 files changed, 97 insertions(+), 5 deletions(-)
 create mode 100644 spec/e2e/pbivizConfigSpec.js

diff --git a/spec/e2e/pbivizConfigSpec.js b/spec/e2e/pbivizConfigSpec.js
new file mode 100644
index 00000000..a414c3e6
--- /dev/null
+++ b/spec/e2e/pbivizConfigSpec.js
@@ -0,0 +1,66 @@
+/*
+ *  Power BI Visual CLI
+ *
+ *  Copyright (c) Microsoft Corporation
+ *  All rights reserved.
+ *  MIT License
+ *
+ *  Permission is hereby granted, free of charge, to any person obtaining a copy
+ *  of this software and associated documentation files (the ""Software""), to deal
+ *  in the Software without restriction, including without limitation the rights
+ *  to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ *  copies of the Software, and to permit persons to whom the Software is
+ *  furnished to do so, subject to the following conditions:
+ *
+ *  The above copyright notice and this permission notice shall be included in
+ *  all copies or substantial portions of the Software.
+ *
+ *  THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ *  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ *  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ *  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ *  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ *  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ *  THE SOFTWARE.
+ */
+
+"use strict";
+
+import { createRequire } from 'module';
+import fs from 'fs-extra';
+import path from 'path';
+import FileSystem from '../helpers/FileSystem.js';
+import { writeMetadataAsJsFile } from "./testUtils.js";
+
+const require = createRequire(import.meta.url);
+const tempPath = path.join(FileSystem.getTempPath(), path.basename(import.meta.url));
+const startPath = process.cwd();
+
+describe("E2E - pbiviz JS config", () => {
+
+    const visualName = 'myjsvisualname';
+    const visualPath = path.join(tempPath, visualName);
+
+    beforeEach(() => {
+        process.chdir(startPath);
+        FileSystem.resetDirectory(tempPath);
+        process.chdir(tempPath);
+        FileSystem.runPbiviz('new', visualName);
+        process.chdir(visualPath);
+
+        writeMetadataAsJsFile(visualPath);
+    });
+
+    afterAll(() => {
+        process.chdir(startPath);
+        FileSystem.deleteDirectory(tempPath);
+    });
+
+    it("Should output visual info from a JS file", () => {
+        const output = FileSystem.runPbiviz('info').toString();
+        const visualConfig = require(path.join(visualPath, 'pbiviz.js')).visual;
+        expect(output).toContain(visualName);
+        expect(output).toContain(visualConfig.guid);
+    });
+});
+
diff --git a/spec/e2e/testUtils.js b/spec/e2e/testUtils.js
index 8141974c..b3865513 100644
--- a/spec/e2e/testUtils.js
+++ b/spec/e2e/testUtils.js
@@ -58,3 +58,17 @@ export const writeMetadata = (visualPath) => {
     pbiviz.author.email = "pbicvsupport";
     fs.writeJSONSync(pbivizJSONFile, pbiviz);
 };
+
+export const writeMetadataAsJsFile = (visualPath) => {
+    const pbivizJSONFile = path.join(visualPath, '/pbiviz.json');
+    const pbivizJSFile = path.join(visualPath, '/pbiviz.js');
+
+    const pbiviz = fs.readJSONSync(pbivizJSONFile);
+    pbiviz.visual.description = "description";
+    pbiviz.visual.supportUrl = "supportUrl";
+    pbiviz.author.name = "Microsoft";
+    pbiviz.author.email = "pbicvsupport";
+
+    const pbivizJSContents = `module.exports = JSON.parse(\`${JSON.stringify(pbiviz)}\`)`;
+    fs.writeFileSync(pbivizJSFile, pbivizJSContents);
+};
diff --git a/src/utils.ts b/src/utils.ts
index b85b882c..9bee4e57 100644
--- a/src/utils.ts
+++ b/src/utils.ts
@@ -26,14 +26,26 @@ export function getRootPath(): string {
     return path.join(pathToDirectory, "..", "..");
 }
 
-function readFileFromRoot(filePath: string) {
-    return fs.readFileSync(path.join(getRootPath(), filePath), "utf8")
+function getJsPath(filePath: string) {
+    return filePath.replace(/\.json$/, '.js');
 }
 
-export function readJsonFromRoot(filePath: string) {
-    return JSON.parse(readFileFromRoot(filePath));
+function safelyRequire(filePath: string) {
+    return fs.existsSync(filePath) && require(filePath);
+}
+
+function safelyParse(filePath: string) {
+    return fs.existsSync(filePath) && JSON.parse(fs.readFileSync(filePath, "utf-8"));
+}
+
+export function readJsonFromRoot(jsonFilename: string) {
+    const jsonPath = path.join(getRootPath(), jsonFilename);
+    const jsPath = getJsPath(jsonPath);
+    return safelyRequire(jsPath) || safelyParse(jsonPath);
 }
 
 export function readJsonFromVisual(filePath: string, visualPath?: string) {
-    return JSON.parse(fs.readFileSync(path.join(visualPath ?? process.cwd(), filePath), "utf8"));
+    const jsonPath = path.join(visualPath ?? process.cwd(), filePath);
+    const jsPath = getJsPath(jsonPath);
+    return safelyRequire(jsPath) || safelyParse(jsonPath);
 }
\ No newline at end of file