powerbi-visuals-tools 7.0.0 → 7.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (48) hide show
  1. package/Changelog.md +4 -0
  2. package/certs/blank +0 -0
  3. package/lib/CertificateTools.js +262 -0
  4. package/lib/CommandManager.js +75 -0
  5. package/lib/ConsoleWriter.js +188 -0
  6. package/lib/FeatureManager.js +43 -0
  7. package/lib/LintValidator.js +61 -0
  8. package/lib/Package.js +46 -0
  9. package/lib/TemplateFetcher.js +111 -0
  10. package/lib/Visual.js +29 -0
  11. package/lib/VisualGenerator.js +221 -0
  12. package/lib/VisualManager.js +316 -0
  13. package/lib/WebPackWrap.js +274 -0
  14. package/lib/features/APIVersion.js +16 -0
  15. package/lib/features/AdvancedEditMode.js +12 -0
  16. package/lib/features/AllowInteractions.js +12 -0
  17. package/lib/features/AnalyticsPane.js +17 -0
  18. package/lib/features/BaseFeature.js +9 -0
  19. package/lib/features/Bookmarks.js +12 -0
  20. package/lib/features/ColorPalette.js +12 -0
  21. package/lib/features/ConditionalFormatting.js +12 -0
  22. package/lib/features/ContextMenu.js +12 -0
  23. package/lib/features/DrillDown.js +16 -0
  24. package/lib/features/FeatureTypes.js +23 -0
  25. package/lib/features/FetchMoreData.js +22 -0
  26. package/lib/features/FileDownload.js +12 -0
  27. package/lib/features/FormatPane.js +12 -0
  28. package/lib/features/HighContrast.js +12 -0
  29. package/lib/features/HighlightData.js +12 -0
  30. package/lib/features/KeyboardNavigation.js +12 -0
  31. package/lib/features/LandingPage.js +12 -0
  32. package/lib/features/LaunchURL.js +12 -0
  33. package/lib/features/LocalStorage.js +12 -0
  34. package/lib/features/Localizations.js +12 -0
  35. package/lib/features/ModalDialog.js +12 -0
  36. package/lib/features/RenderingEvents.js +13 -0
  37. package/lib/features/SelectionAcrossVisuals.js +12 -0
  38. package/lib/features/SyncSlicer.js +12 -0
  39. package/lib/features/Tooltips.js +12 -0
  40. package/lib/features/TotalSubTotal.js +12 -0
  41. package/lib/features/VisualVersion.js +12 -0
  42. package/lib/features/WarningIcon.js +12 -0
  43. package/lib/features/index.js +28 -0
  44. package/lib/utils.js +43 -0
  45. package/lib/webpack.config.js +169 -0
  46. package/package.json +7 -6
  47. package/.eslintrc.json +0 -21
  48. package/CVClientLA.md +0 -85
package/Changelog.md CHANGED
@@ -2,6 +2,10 @@
2
2
 
3
3
  This page contains information about changes to the PowerBI Visual Tools (pbiviz).
4
4
 
5
+ ## 7.0.1
6
+ * Fixed issue with missing `lib/*` files in latest versions.
7
+ * Updated packages
8
+
5
9
  ## 7.0.0
6
10
  * **BREAKING CHANGE**: Removed Node.js polyfills from webpack configuration for improved security and reduced bundle size. Node.js modules (crypto, buffer, stream, etc.) are no longer automatically available in the browser environment.
7
11
  * Removed browserify polyfill packages from dependencies (assert, buffer, crypto-browserify, console-browserify, constants-browserify, domain-browser, events, https-browserify, os-browserify, path-browserify, process, punycode, querystring-es3, readable-stream, stream-browserify, stream-http, string_decoder, timers-browserify, tty-browserify, url, util, vm-browserify, browserify-zlib)
package/certs/blank ADDED
File without changes
@@ -0,0 +1,262 @@
1
+ /*
2
+ * Power BI Visual CLI
3
+ *
4
+ * Copyright (c) Microsoft Corporation
5
+ * All rights reserved.
6
+ * MIT License
7
+ *
8
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
9
+ * of this software and associated documentation files (the ""Software""), to deal
10
+ * in the Software without restriction, including without limitation the rights
11
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
12
+ * copies of the Software, and to permit persons to whom the Software is
13
+ * furnished to do so, subject to the following conditions:
14
+ *
15
+ * The above copyright notice and this permission notice shall be included in
16
+ * all copies or substantial portions of the Software.
17
+ *
18
+ * THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
24
+ * THE SOFTWARE.
25
+ */
26
+ "use strict";
27
+ import { exec as nodeExec } from 'child_process';
28
+ import fs from 'fs-extra';
29
+ import os from 'os';
30
+ import path from 'path';
31
+ import crypto from "crypto";
32
+ import { readJsonFromRoot } from './utils.js';
33
+ import ConsoleWriter from './ConsoleWriter.js';
34
+ const certSafePeriod = 1000 * 60 * 60 * 24; // 24 hours
35
+ const config = await readJsonFromRoot('config.json');
36
+ const pathToCertFolder = path.join(os.homedir(), config.server.certificateFolder);
37
+ const secretFilesPath = {
38
+ certPath: path.join(pathToCertFolder, config.server.certificate),
39
+ keyPath: path.join(pathToCertFolder, config.server.privateKey),
40
+ pfxPath: path.join(pathToCertFolder, config.server.pfx),
41
+ passphrasePath: path.join(pathToCertFolder, config.server.passphrase)
42
+ };
43
+ function exec(command, options, callback) {
44
+ return new Promise((resolve, reject) => {
45
+ const defaultCallback = (err, stdout, stderr) => {
46
+ if (err) {
47
+ reject(stderr);
48
+ }
49
+ resolve(stdout);
50
+ };
51
+ nodeExec(command, options, callback ? callback : defaultCallback);
52
+ });
53
+ }
54
+ export async function createCertificate() {
55
+ const certPath = await getCertFile(true);
56
+ if (!certPath) {
57
+ ConsoleWriter.error("Certificate not found. The new certificate will be generated");
58
+ await createCertFile(true);
59
+ }
60
+ else {
61
+ await openCertFile();
62
+ }
63
+ }
64
+ export async function createCertFile(open = false) {
65
+ ConsoleWriter.info(`Generating a new certificate...`);
66
+ const subject = "localhost";
67
+ const keyLength = 2048;
68
+ const validPeriod = 365;
69
+ const { certPath, keyPath, pfxPath } = secretFilesPath;
70
+ const openCmds = {
71
+ linux: 'openssl',
72
+ darwin: 'openssl',
73
+ win32: 'pwsh'
74
+ };
75
+ let startCmd = openCmds[os.platform()];
76
+ if (!startCmd) {
77
+ ConsoleWriter.error(['Unknown platform. Please place a custom-generated certificate in:', certPath]);
78
+ return;
79
+ }
80
+ try {
81
+ let createCertCommand = "";
82
+ let passphrase = "";
83
+ fs.ensureDirSync(path.dirname(certPath));
84
+ switch (os.platform()) {
85
+ case "linux":
86
+ case "darwin":
87
+ createCertCommand =
88
+ ` req -newkey rsa:${keyLength}` +
89
+ ` -nodes` +
90
+ ` -keyout ${keyPath}` +
91
+ ` -x509 ` +
92
+ ` -days ${validPeriod} ` +
93
+ ` -out ${certPath} ` +
94
+ ` -subj "/CN=${subject}"`;
95
+ await Promise.all([
96
+ removeCertFiles(certPath, keyPath),
97
+ exec(`${startCmd} ${createCertCommand}`)
98
+ ]);
99
+ if (await fs.exists(certPath)) {
100
+ ConsoleWriter.info(`Certificate generated. Location is ${certPath}`);
101
+ if (open) {
102
+ await openCertFile();
103
+ }
104
+ }
105
+ break;
106
+ case "win32":
107
+ passphrase = getRandomValues()[0].toString().substring(2);
108
+ createCertCommand = `$cert = ('Cert:\\CurrentUser\\My\\' + (` +
109
+ ` New-SelfSignedCertificate ` +
110
+ ` -DnsName localhost ` +
111
+ ` -Type Custom ` +
112
+ ` -Subject 'CN=${subject}' ` +
113
+ ` -KeyAlgorithm RSA ` +
114
+ ` -KeyLength ${keyLength} ` +
115
+ ` -KeyExportPolicy Exportable ` +
116
+ ` -CertStoreLocation Cert:\\CurrentUser\\My ` +
117
+ ` -NotAfter (get-date).AddDays(${validPeriod}) ` +
118
+ ` | select Thumbprint | ` +
119
+ ` ForEach-Object { $_.Thumbprint.ToString() }).toString()); ` +
120
+ ` Export-PfxCertificate -Cert $cert` +
121
+ ` -FilePath '${pfxPath}' ` +
122
+ ` -Password (ConvertTo-SecureString -String '${passphrase}' -Force -AsPlainText)`;
123
+ await Promise.all([
124
+ removeCertFiles(certPath, keyPath, pfxPath),
125
+ exec(`${startCmd} -Command "${createCertCommand}"`),
126
+ savePassphrase(passphrase)
127
+ ]);
128
+ if (await fs.exists(pfxPath)) {
129
+ ConsoleWriter.info(`Certificate generated. Location is ${pfxPath}. Passphrase is ${passphrase}`);
130
+ }
131
+ break;
132
+ default:
133
+ ConsoleWriter.error('Unknown platform');
134
+ break;
135
+ }
136
+ }
137
+ catch (e) {
138
+ if (e && e.message && e.message.indexOf("'openssl' is not recognized as an internal or external command") > 0) {
139
+ ConsoleWriter.warning('Create certificate error:');
140
+ ConsoleWriter.warning('OpenSSL is not installed or not available from command line');
141
+ ConsoleWriter.info('Install OpenSSL from https://www.openssl.org or https://wiki.openssl.org/index.php/Binaries');
142
+ ConsoleWriter.info('and try again');
143
+ ConsoleWriter.info('Read more at');
144
+ ConsoleWriter.info('https://github.com/Microsoft/PowerBI-visuals/blob/master/tools/CreateCertificate.md#manual');
145
+ }
146
+ else {
147
+ ConsoleWriter.error(['Create certificate error:', e]);
148
+ }
149
+ }
150
+ }
151
+ async function getCertFile(silent = false) {
152
+ const { certPath, pfxPath, passphrasePath } = secretFilesPath;
153
+ if (await fs.exists(certPath)) {
154
+ return certPath;
155
+ }
156
+ if (await fs.exists(pfxPath)) {
157
+ if (!silent && await fs.exists(passphrasePath)) {
158
+ const passphrase = await fs.readFile(passphrasePath, 'utf8');
159
+ ConsoleWriter.info(`Use '${passphrase}' passphrase to install PFX certificate.`);
160
+ }
161
+ return pfxPath;
162
+ }
163
+ if (!silent) {
164
+ ConsoleWriter.info('Certificate not found. Call `pbiviz install-cert` command to create the new certificate');
165
+ }
166
+ return null;
167
+ }
168
+ async function openCertFile() {
169
+ const openCmds = {
170
+ linux: 'xdg-open',
171
+ darwin: 'open',
172
+ win32: 'powershell start'
173
+ };
174
+ const startCmd = openCmds[os.platform()];
175
+ const certPath = await getCertFile();
176
+ try {
177
+ if (!startCmd || !certPath) {
178
+ throw new Error();
179
+ }
180
+ await exec(`${startCmd} "${certPath}"`);
181
+ }
182
+ catch (e) {
183
+ ConsoleWriter.info(['Certificate path:', certPath]);
184
+ }
185
+ }
186
+ export async function removeCertFiles(...paths) {
187
+ paths.forEach(async (path) => {
188
+ try {
189
+ await fs.unlink(path);
190
+ }
191
+ catch (e) {
192
+ if (!e.message.indexOf("no such file or directory")) {
193
+ throw e;
194
+ }
195
+ }
196
+ });
197
+ }
198
+ export async function resolveCertificate() {
199
+ const options = {};
200
+ const { certPath, keyPath, pfxPath, passphrasePath } = secretFilesPath;
201
+ const isCertificateValid = await verifyCertFile(keyPath, certPath, pfxPath, passphrasePath);
202
+ if (!isCertificateValid) {
203
+ await createCertFile();
204
+ }
205
+ if (await fs.exists(passphrasePath)) {
206
+ options.passphrase = await fs.readFile(passphrasePath, 'utf8');
207
+ }
208
+ if (await fs.exists(keyPath)) {
209
+ options.key = await fs.readFile(keyPath);
210
+ }
211
+ if (await fs.exists(certPath)) {
212
+ options.cert = await fs.readFile(certPath);
213
+ }
214
+ if (await fs.exists(pfxPath)) {
215
+ options.pfx = await fs.readFile(pfxPath);
216
+ }
217
+ return options;
218
+ }
219
+ export async function verifyCertFile(keyPath, certPath, pfxPath, passphrasePath) {
220
+ let verifyCertDate = false;
221
+ try {
222
+ let endDateStr;
223
+ if (os.platform() === "win32") {
224
+ if (!fs.existsSync(pfxPath) || !fs.existsSync(passphrasePath)) {
225
+ throw new Error('PFX or passphrase file not found');
226
+ }
227
+ const passphrase = await fs.readFile(passphrasePath, 'utf8');
228
+ const command = `(New-Object System.Security.Cryptography.X509Certificates.X509Certificate2("${pfxPath}",'${passphrase}')).NotAfter.ToString('yyyy-MM-dd HH:mm:ss')`;
229
+ const certStr = await exec(command, { shell: 'powershell.exe' });
230
+ endDateStr = certStr.trim();
231
+ }
232
+ else if (os.platform() === "linux" || os.platform() === "darwin") {
233
+ if (!(await fs.exists(certPath))) {
234
+ throw new Error('Certificate file not found');
235
+ }
236
+ endDateStr = await exec(`openssl x509 -enddate -noout -in ${certPath} | cut -d = -f 2`);
237
+ }
238
+ const endDate = Date.parse(endDateStr);
239
+ verifyCertDate = (endDate - Date.now()) > certSafePeriod;
240
+ if (verifyCertDate) {
241
+ ConsoleWriter.info(`Certificate is valid.`);
242
+ }
243
+ else {
244
+ throw new Error('Certificate is invalid');
245
+ }
246
+ }
247
+ catch (err) {
248
+ ConsoleWriter.warning(`Certificate verification error: ${err}`);
249
+ removeCertFiles(certPath, keyPath, pfxPath, passphrasePath);
250
+ }
251
+ return verifyCertDate;
252
+ }
253
+ const getRandomValues = () => {
254
+ if (crypto.getRandomValues !== undefined) {
255
+ return crypto.getRandomValues(new Uint32Array(1));
256
+ }
257
+ return crypto.webcrypto.getRandomValues(new Uint32Array(1));
258
+ };
259
+ const savePassphrase = async (passphrase) => {
260
+ const { passphrasePath } = secretFilesPath;
261
+ await fs.writeFileSync(passphrasePath, passphrase);
262
+ };
@@ -0,0 +1,75 @@
1
+ import { createCertificate } from './CertificateTools.js';
2
+ import ConsoleWriter from './ConsoleWriter.js';
3
+ import VisualManager from './VisualManager.js';
4
+ export default class CommandManager {
5
+ static async start(options, rootPath) {
6
+ const webpackOptions = {
7
+ devMode: true,
8
+ devtool: "inline-source-map",
9
+ generateResources: true,
10
+ generatePbiviz: false,
11
+ minifyJS: false,
12
+ minify: false,
13
+ devServerPort: options.port,
14
+ stats: options.stats,
15
+ skipApiCheck: options.skipApi,
16
+ allLocales: options.allLocales,
17
+ pbivizFile: options.pbivizFile,
18
+ };
19
+ const visualManager = new VisualManager(rootPath);
20
+ await visualManager.prepareVisual(options.pbivizFile);
21
+ await visualManager.validateVisual();
22
+ await visualManager.initializeWebpack(webpackOptions);
23
+ visualManager.startWebpackServer(options.drop);
24
+ }
25
+ static async lint(options, rootPath) {
26
+ const visualManager = new VisualManager(rootPath);
27
+ await visualManager.prepareVisual();
28
+ await visualManager.runLintValidation(options);
29
+ }
30
+ static async package(options, rootPath) {
31
+ if (!options.pbiviz && !options.resources) {
32
+ ConsoleWriter.error('Nothing to build. Cannot use --no-pbiviz without --resources');
33
+ process.exit(1);
34
+ }
35
+ const webpackOptions = {
36
+ devMode: false,
37
+ generateResources: options.resources,
38
+ generatePbiviz: options.pbiviz,
39
+ minifyJS: options.minify,
40
+ minify: options.minify,
41
+ compression: options.compression,
42
+ stats: options.stats,
43
+ skipApiCheck: options.skipApi,
44
+ allLocales: options.allLocales,
45
+ pbivizFile: options.pbivizFile,
46
+ certificationAudit: options.certificationAudit,
47
+ certificationFix: options.certificationFix,
48
+ };
49
+ const lintOptions = {
50
+ verbose: options.verbose,
51
+ fix: options.fix,
52
+ };
53
+ const visualManager = new VisualManager(rootPath);
54
+ const visual = await visualManager.prepareVisual(options.pbivizFile);
55
+ await visual.runLintValidation(lintOptions);
56
+ await visual.validateVisual(options.verbose);
57
+ await visual.initializeWebpack(webpackOptions)
58
+ .then(manager => manager.generatePackage(options.verbose));
59
+ }
60
+ static new({ force, template }, name, rootPath) {
61
+ const generateOptions = {
62
+ force: force,
63
+ template: template
64
+ };
65
+ VisualManager.createVisual(rootPath, name, generateOptions);
66
+ }
67
+ static async info(rootPath) {
68
+ const visualManager = new VisualManager(rootPath);
69
+ await visualManager.prepareVisual();
70
+ await visualManager.displayInfo();
71
+ }
72
+ static async installCert() {
73
+ await createCertificate();
74
+ }
75
+ }
@@ -0,0 +1,188 @@
1
+ /*
2
+ * Power BI Visual CLI
3
+ *
4
+ * Copyright (c) Microsoft Corporation
5
+ * All rights reserved.
6
+ * MIT License
7
+ *
8
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
9
+ * of this software and associated documentation files (the ""Software""), to deal
10
+ * in the Software without restriction, including without limitation the rights
11
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
12
+ * copies of the Software, and to permit persons to whom the Software is
13
+ * furnished to do so, subject to the following conditions:
14
+ *
15
+ * The above copyright notice and this permission notice shall be included in
16
+ * all copies or substantial portions of the Software.
17
+ *
18
+ * THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
24
+ * THE SOFTWARE.
25
+ */
26
+ "use strict";
27
+ import chalk from 'chalk';
28
+ import fs from 'fs';
29
+ import os from 'os';
30
+ import path from 'path';
31
+ import { getRootPath } from './utils.js';
32
+ const preferredChalk = os.platform() === 'darwin' ? chalk.bold : chalk;
33
+ function prependLogTag(tag, args) {
34
+ return [tag].concat(args);
35
+ }
36
+ export default class ConsoleWriter {
37
+ /** Causes the terminal to beep */
38
+ static beep() {
39
+ process.stdout.write("\x07");
40
+ }
41
+ /** Outputs a blank line */
42
+ static blank() {
43
+ console.info(preferredChalk.reset(' '));
44
+ }
45
+ /**
46
+ * Outputs arguments with the "done" tag / colors
47
+ *
48
+ * @param {array} arguments - arguments passed through to console.info
49
+ */
50
+ static done(args) {
51
+ const tag = preferredChalk.bgGreen(' done ');
52
+ console.info.apply(this, prependLogTag(tag, args));
53
+ }
54
+ /**
55
+ * Outputs arguments with the "info" tag / colors
56
+ *
57
+ * @param {array} args - arguments passed through to console.info
58
+ */
59
+ static info(args) {
60
+ const tag = preferredChalk.bgCyan(' info ');
61
+ console.info.apply(this, prependLogTag(tag, args));
62
+ }
63
+ /**
64
+ * Outputs arguments with the "warn" tag / colors
65
+ *
66
+ * @param {array} args - arguments passed through to console.warn
67
+ */
68
+ static warning(args) {
69
+ const tag = preferredChalk.bgYellow.black(' warn ');
70
+ console.warn.apply(this, prependLogTag(tag, args));
71
+ }
72
+ /**
73
+ * Outputs arguments with the "error" tag / colors
74
+ *
75
+ * @param {array} args - arguments passed through to console.error
76
+ */
77
+ static error(args) {
78
+ const tag = preferredChalk.bgRed(' error ');
79
+ console.error.apply(this, prependLogTag(tag, args));
80
+ }
81
+ /**
82
+ * Outputs an object as a table
83
+ *
84
+ * @param {string} data - object to output
85
+ * @param {number} [depthLimit=Infinity] - limit the number of levels to recurse
86
+ * @param {string} [keyPrefix=''] - text to prepend to each key
87
+ */
88
+ static infoTable(data, depthLimit, keyPrefix) {
89
+ if (!data) {
90
+ return;
91
+ }
92
+ const limit = typeof depthLimit === 'undefined' ? Infinity : depthLimit;
93
+ for (const key in data) {
94
+ const item = data[key];
95
+ const itemKey = (keyPrefix || '') + key;
96
+ if (limit > 1 && typeof item === 'object' && !Array.isArray(item)) {
97
+ ConsoleWriter.infoTable(item, limit - 1, itemKey + '.');
98
+ }
99
+ else {
100
+ ConsoleWriter.infoTableRow(itemKey, item);
101
+ }
102
+ }
103
+ }
104
+ /**
105
+ * Outputs a table row with even spaced keys
106
+ *
107
+ * @param {string} key - title of this row
108
+ * @param {string} value - value for this row
109
+ * @param {number} [keyWidth=30] - width used for padding of the key column
110
+ */
111
+ static infoTableRow(key, value, keyWidth) {
112
+ const width = keyWidth || 30;
113
+ const padding = Math.max(0, width - key.length);
114
+ const paddedKey = preferredChalk.bold(key) + (new Array(padding)).join('.');
115
+ ConsoleWriter.info([paddedKey, value]);
116
+ }
117
+ /**
118
+ * Outputs formatted errors
119
+ *
120
+ * @param {Array<Error>} errors
121
+ */
122
+ static formattedErrors(errors) {
123
+ if (errors && Array.isArray(errors)) {
124
+ errors.forEach((error) => {
125
+ if (!error) {
126
+ return;
127
+ }
128
+ const tag = error.type ? preferredChalk.bold(error.type.toUpperCase()) : 'UNKNOWN';
129
+ const file = error.filename ? preferredChalk.bgWhite.black(` ${error.filename} `) + ':' : '';
130
+ const position = (error.line && error.column) ? preferredChalk.cyan(`(${error.line},${error.column})`) : '';
131
+ const message = error.message || '';
132
+ ConsoleWriter.error([tag, `${file} ${position} ${message}`]);
133
+ });
134
+ }
135
+ else {
136
+ ConsoleWriter.error(['UNKNOWN', errors]);
137
+ }
138
+ }
139
+ /**
140
+ * Outputs ascii art of the PowerBI logo
141
+ */
142
+ static getLogoVisualization() {
143
+ return fs.readFileSync(path.join(getRootPath(), 'assets', 'logo.txt')).toString();
144
+ }
145
+ /**
146
+ * Outputs validation log from PBIVIZ package checking
147
+ */
148
+ static validationLog(log) {
149
+ // api/js/css/pkg
150
+ const filterChecks = (attrCB, propCB) => {
151
+ for (const checkname in log) {
152
+ if (checkname !== 'valid') {
153
+ const checkpoint = log[checkname];
154
+ ConsoleWriter[checkpoint.error.length ? 'info' : 'done'](checkpoint.check);
155
+ attrCB(checkpoint, propCB);
156
+ }
157
+ }
158
+ };
159
+ // error/message/ok
160
+ const filterCheckAttrs = (checkpoint, propCB) => {
161
+ for (const propName in checkpoint) {
162
+ if (propName !== 'message') {
163
+ const prop = checkpoint[propName];
164
+ if (typeof (prop) === 'object' && prop.length) {
165
+ propCB(prop, propName);
166
+ }
167
+ }
168
+ }
169
+ };
170
+ // col/line/text
171
+ const filterAttrProps = (props, propName) => {
172
+ props.forEach((opt) => {
173
+ const result = [];
174
+ for (const key in opt) {
175
+ result.push(opt[key]);
176
+ }
177
+ if (result.length) {
178
+ ConsoleWriter[propName === 'error' ? 'error' : 'warn'](result.join(' --> '));
179
+ }
180
+ });
181
+ };
182
+ filterChecks(filterCheckAttrs, filterAttrProps);
183
+ const type = log.valid ? 'done' : 'error';
184
+ const text = log.valid ? 'Valid package' : 'Invalid package';
185
+ ConsoleWriter.blank();
186
+ ConsoleWriter[type](text);
187
+ }
188
+ }
@@ -0,0 +1,43 @@
1
+ import { Severity } from "./features/FeatureTypes.js";
2
+ import * as features from "./features/index.js";
3
+ export var Status;
4
+ (function (Status) {
5
+ Status[Status["Success"] = 0] = "Success";
6
+ Status[Status["Error"] = 1] = "Error";
7
+ })(Status || (Status = {}));
8
+ export class FeatureManager {
9
+ features = Object.keys(features).map(key => features[key]);
10
+ validate(stage, sourceInstance) {
11
+ const result = {
12
+ status: Status.Success,
13
+ logs: {
14
+ errors: [],
15
+ warnings: [],
16
+ info: [],
17
+ deprecation: []
18
+ }
19
+ };
20
+ this.features
21
+ .filter(feature => feature.stage == stage)
22
+ .filter(feature => feature.visualFeatureType & sourceInstance.visualFeatureType)
23
+ .filter(feature => !feature.isSupported(sourceInstance))
24
+ .forEach(({ errorMessage, severity }) => {
25
+ switch (severity) {
26
+ case Severity.Error:
27
+ result.status = Status.Error;
28
+ result.logs.errors.push(errorMessage);
29
+ break;
30
+ case Severity.Warning:
31
+ result.logs.warnings.push(errorMessage);
32
+ break;
33
+ case Severity.Info:
34
+ result.logs.info.push(errorMessage);
35
+ break;
36
+ case Severity.Deprecation:
37
+ result.logs.deprecation.push(errorMessage);
38
+ break;
39
+ }
40
+ });
41
+ return result;
42
+ }
43
+ }
@@ -0,0 +1,61 @@
1
+ import { ESLint } from "eslint";
2
+ import powerbiPlugin from 'eslint-plugin-powerbi-visuals';
3
+ import ConsoleWriter from "./ConsoleWriter.js";
4
+ import { getRootPath } from "./utils.js";
5
+ export class LintValidator {
6
+ visualPath;
7
+ rootPath;
8
+ isVerboseMode;
9
+ shouldFix;
10
+ config;
11
+ linterInstance;
12
+ constructor({ verbose, fix }) {
13
+ this.visualPath = process.cwd();
14
+ this.rootPath = getRootPath();
15
+ this.isVerboseMode = verbose;
16
+ this.shouldFix = fix;
17
+ this.prepareConfig();
18
+ this.linterInstance = new ESLint(this.config);
19
+ }
20
+ /**
21
+ * Runs lint validation in the visual folder
22
+ */
23
+ async runLintValidation() {
24
+ ConsoleWriter.info("Running lint check...");
25
+ // By default it will lint all files in the src of current working directory
26
+ const results = await this.linterInstance.lintFiles("src/");
27
+ if (this.shouldFix) {
28
+ await this.fixErrors(results);
29
+ }
30
+ await this.outputResults(results);
31
+ ConsoleWriter.info("Lint check completed.");
32
+ }
33
+ async fixErrors(results) {
34
+ ConsoleWriter.info("Lint fixing errors...");
35
+ await ESLint.outputFixes(results);
36
+ }
37
+ async outputResults(results) {
38
+ if (this.isVerboseMode) {
39
+ const formatter = await this.linterInstance.loadFormatter("stylish");
40
+ const formattedResults = await formatter.format(results);
41
+ console.log(formattedResults);
42
+ }
43
+ else {
44
+ const filteredResults = ESLint.getErrorResults(results);
45
+ // get total amount of errors and warnings in all elements of filteredResults
46
+ const totalErrors = filteredResults.reduce((acc, curr) => acc + curr.errorCount, 0);
47
+ const totalWarnings = filteredResults.reduce((acc, curr) => acc + curr.warningCount, 0);
48
+ if (totalErrors > 0 || totalWarnings > 0) {
49
+ ConsoleWriter.error(`Linter found ${totalErrors} errors and ${totalWarnings} warnings. Run with --verbose flag to see details.`);
50
+ }
51
+ }
52
+ }
53
+ prepareConfig() {
54
+ ConsoleWriter.warning("Using recommended eslint config.");
55
+ this.config = {
56
+ overrideConfig: powerbiPlugin.configs.recommended,
57
+ overrideConfigFile: true,
58
+ fix: this.shouldFix,
59
+ };
60
+ }
61
+ }
package/lib/Package.js ADDED
@@ -0,0 +1,46 @@
1
+ /*
2
+ * Power BI Visual CLI
3
+ *
4
+ * Copyright (c) Microsoft Corporation
5
+ * All rights reserved.
6
+ * MIT License
7
+ *
8
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
9
+ * of this software and associated documentation files (the ""Software""), to deal
10
+ * in the Software without restriction, including without limitation the rights
11
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
12
+ * copies of the Software, and to permit persons to whom the Software is
13
+ * furnished to do so, subject to the following conditions:
14
+ *
15
+ * The above copyright notice and this permission notice shall be included in
16
+ * all copies or substantial portions of the Software.
17
+ *
18
+ * THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
24
+ * THE SOFTWARE.
25
+ */
26
+ "use strict";
27
+ import isMatch from "lodash.ismatch";
28
+ /**
29
+ * Represents an instance of a visual package based on file path
30
+ */
31
+ export default class Package {
32
+ sourceCode;
33
+ capabilities;
34
+ visualFeatureType;
35
+ constructor(sourceCode, capabilities, visualFeatureType) {
36
+ this.sourceCode = sourceCode;
37
+ this.capabilities = capabilities;
38
+ this.visualFeatureType = visualFeatureType;
39
+ }
40
+ contain(keyword) {
41
+ return this.sourceCode.includes(keyword);
42
+ }
43
+ isCapabilityEnabled(expectedObject) {
44
+ return isMatch(this.capabilities, expectedObject);
45
+ }
46
+ }