@datashow/super-visual-cli 0.1.0-rc.1 → 0.1.0-rc.3
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.
- package/bin/supervisual.mjs +9 -2
- package/package.json +5 -5
- package/src/main.mjs +67 -9
- package/src/project.mjs +2 -0
- package/templates/blank/package.json +1 -1
- package/templates/hello-world/package.json +1 -1
package/bin/supervisual.mjs
CHANGED
|
@@ -1,9 +1,16 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { main } from "../src/main.mjs";
|
|
2
|
+
import { main, stopActiveProgress } from "../src/main.mjs";
|
|
3
3
|
|
|
4
4
|
main().catch((error) => {
|
|
5
|
+
stopActiveProgress();
|
|
5
6
|
const json = process.argv.includes("--json");
|
|
6
7
|
const message = error instanceof Error ? error.message : String(error);
|
|
7
|
-
|
|
8
|
+
const summary = error?.summary ?? "Super Visual could not complete the command.";
|
|
9
|
+
const guidance = error?.guidance;
|
|
10
|
+
if (json) {
|
|
11
|
+
process.stderr.write(`${JSON.stringify({ ok: false, message: summary, ...(guidance ? { guidance } : {}), error: message })}\n`);
|
|
12
|
+
} else {
|
|
13
|
+
process.stderr.write(`\n${summary}\n${guidance ? `${guidance}\n` : ""}\nTechnical details: ${message}\n`);
|
|
14
|
+
}
|
|
8
15
|
process.exitCode = 1;
|
|
9
16
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@datashow/super-visual-cli",
|
|
3
|
-
"version": "0.1.0-rc.
|
|
3
|
+
"version": "0.1.0-rc.3",
|
|
4
4
|
"description": "CLI for validating, compiling, packaging, verifying, and deploying Super Visual projects.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -25,10 +25,10 @@
|
|
|
25
25
|
"access": "public"
|
|
26
26
|
},
|
|
27
27
|
"dependencies": {
|
|
28
|
-
"@datashow/super-visual-adapter-claude-dashboard": "0.1.0-rc.
|
|
29
|
-
"@datashow/super-visual-compiler": "0.1.0-rc.
|
|
30
|
-
"@datashow/super-visual-contracts": "0.1.0-rc.
|
|
31
|
-
"@datashow/super-visual-fabric": "0.1.0-rc.
|
|
28
|
+
"@datashow/super-visual-adapter-claude-dashboard": "0.1.0-rc.3",
|
|
29
|
+
"@datashow/super-visual-compiler": "0.1.0-rc.3",
|
|
30
|
+
"@datashow/super-visual-contracts": "0.1.0-rc.3",
|
|
31
|
+
"@datashow/super-visual-fabric": "0.1.0-rc.3",
|
|
32
32
|
"@fontsource/source-serif-4": "5.3.1",
|
|
33
33
|
"@nivo/bar": "0.99.0",
|
|
34
34
|
"@nivo/line": "0.99.0",
|
package/src/main.mjs
CHANGED
|
@@ -15,7 +15,30 @@ import { resolveProjectTarget, targetUpdate, writeProjectTarget } from "./projec
|
|
|
15
15
|
import { runSemanticPreviewTest, startSemanticPreview } from "./semantic-preview.mjs";
|
|
16
16
|
|
|
17
17
|
const run = promisify(execFile);
|
|
18
|
-
const VERSION = "0.1.0-rc.
|
|
18
|
+
const VERSION = "0.1.0-rc.3";
|
|
19
|
+
const FABRIC_ID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
20
|
+
const SPINNER_FRAMES = ["|", "/", "-", "\\"];
|
|
21
|
+
const progressStream = process.stderr;
|
|
22
|
+
let activeProgress = null;
|
|
23
|
+
|
|
24
|
+
export class CliUserError extends Error {
|
|
25
|
+
constructor(message, { summary, guidance } = {}) {
|
|
26
|
+
super(message);
|
|
27
|
+
this.name = "CliUserError";
|
|
28
|
+
this.summary = summary;
|
|
29
|
+
this.guidance = guidance;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export function validateFabricId(value, optionName) {
|
|
34
|
+
if (value === undefined) return;
|
|
35
|
+
if (FABRIC_ID_PATTERN.test(value)) return;
|
|
36
|
+
const target = optionName === "--workspace-id" ? "workspace" : "semantic model";
|
|
37
|
+
throw new CliUserError(`${optionName} must be a 36-character GUID in 8-4-4-4-12 format; received "${value}" (${String(value).length} characters).`, {
|
|
38
|
+
summary: `Super Visual could not initialize the project because the ${target} ID is not valid.`,
|
|
39
|
+
guidance: `Copy the complete ${target} ID from Fabric and run the command again.`,
|
|
40
|
+
});
|
|
41
|
+
}
|
|
19
42
|
|
|
20
43
|
function projectFile(input = ".") {
|
|
21
44
|
const resolved = path.resolve(input);
|
|
@@ -26,9 +49,33 @@ function output(value, json) {
|
|
|
26
49
|
process.stdout.write(`${json ? JSON.stringify(value) : typeof value === "string" ? value : JSON.stringify(value, null, 2)}\n`);
|
|
27
50
|
}
|
|
28
51
|
|
|
29
|
-
function
|
|
30
|
-
|
|
31
|
-
|
|
52
|
+
export function stopActiveProgress() {
|
|
53
|
+
if (!activeProgress) return;
|
|
54
|
+
clearInterval(activeProgress.timer);
|
|
55
|
+
progressStream.write(`\r${" ".repeat(activeProgress.width)}\r`);
|
|
56
|
+
activeProgress = null;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export function writeInitProgress(event) {
|
|
60
|
+
if (event.status === "running") {
|
|
61
|
+
stopActiveProgress();
|
|
62
|
+
progressStream.write(`> ${event.message}\n`);
|
|
63
|
+
if (!progressStream.isTTY) return;
|
|
64
|
+
let frame = 0;
|
|
65
|
+
const render = () => {
|
|
66
|
+
const line = `${SPINNER_FRAMES[frame % SPINNER_FRAMES.length]} Working...`;
|
|
67
|
+
progressStream.write(`\r${line}`);
|
|
68
|
+
activeProgress.width = Math.max(activeProgress.width, line.length);
|
|
69
|
+
frame += 1;
|
|
70
|
+
};
|
|
71
|
+
activeProgress = { timer: null, width: 12 };
|
|
72
|
+
render();
|
|
73
|
+
activeProgress.timer = setInterval(render, 120);
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
stopActiveProgress();
|
|
77
|
+
const marker = event.status === "passed" ? "✓" : "!";
|
|
78
|
+
progressStream.write(`${marker} ${event.message}\n`);
|
|
32
79
|
}
|
|
33
80
|
|
|
34
81
|
function semanticInventory(model) {
|
|
@@ -275,13 +322,23 @@ function createProgram() {
|
|
|
275
322
|
.option("--force", "write into an existing empty directory")
|
|
276
323
|
.action(async (directory, options, command) => {
|
|
277
324
|
if ((options.workspaceName || options.semanticModelId || options.reportName || options.connectionId) && !options.workspaceId) throw new Error("--workspace-id is required when saving a Fabric binding");
|
|
325
|
+
validateFabricId(options.workspaceId, "--workspace-id");
|
|
326
|
+
validateFabricId(options.semanticModelId, "--semantic-model-id");
|
|
278
327
|
const json = jsonEnabled(command);
|
|
279
|
-
if (!json)
|
|
328
|
+
if (!json) progressStream.write(`\nSuper Visual init\n\n`);
|
|
280
329
|
let liveValidation = null;
|
|
281
330
|
if (options.workspaceId && options.semanticModelId && !options.skipLiveValidation) {
|
|
282
331
|
if (!json) writeInitProgress({ status: "running", message: "Verifying the Fabric workspace and Power BI semantic model (read-only)" });
|
|
283
|
-
|
|
284
|
-
|
|
332
|
+
let inspected;
|
|
333
|
+
try {
|
|
334
|
+
const client = new FabricHttpClient({ timeoutSeconds: Number(options.timeout) });
|
|
335
|
+
inspected = await inspectSemanticModel({ client, workspaceId: options.workspaceId, workspaceName: options.workspaceName, semanticModelId: options.semanticModelId });
|
|
336
|
+
} catch (error) {
|
|
337
|
+
throw new CliUserError(error instanceof Error ? error.message : String(error), {
|
|
338
|
+
summary: "Super Visual could not verify the Fabric workspace and semantic model.",
|
|
339
|
+
guidance: "Confirm that you are signed in, can access the workspace, and copied both IDs from the same Fabric workspace.",
|
|
340
|
+
});
|
|
341
|
+
}
|
|
285
342
|
liveValidation = {
|
|
286
343
|
ok: true,
|
|
287
344
|
status: "passed",
|
|
@@ -292,9 +349,9 @@ function createProgram() {
|
|
|
292
349
|
};
|
|
293
350
|
if (!json) writeInitProgress({ status: "passed", message: `Verified workspace ${inspected.workspace.displayName} and semantic model ${inspected.semanticModel.displayName}` });
|
|
294
351
|
} else if (!json && options.skipLiveValidation) {
|
|
295
|
-
|
|
352
|
+
progressStream.write("! Live Fabric validation skipped by request\n");
|
|
296
353
|
} else if (!json) {
|
|
297
|
-
|
|
354
|
+
progressStream.write("! No complete Fabric binding supplied; live validation was not run\n");
|
|
298
355
|
}
|
|
299
356
|
const result = await initializeProject({
|
|
300
357
|
directory,
|
|
@@ -306,6 +363,7 @@ function createProgram() {
|
|
|
306
363
|
onProgress: json ? null : writeInitProgress,
|
|
307
364
|
});
|
|
308
365
|
if (options.workspaceId) {
|
|
366
|
+
if (!json) writeInitProgress({ status: "running", message: "Saving the reusable Fabric project binding" });
|
|
309
367
|
const saved = await writeProjectTarget(result.directory, targetUpdate({
|
|
310
368
|
...options,
|
|
311
369
|
workspaceName: options.workspaceName ?? liveValidation?.workspace.displayName,
|
package/src/project.mjs
CHANGED
|
@@ -286,9 +286,11 @@ export async function initializeProject({ directory, template = "blank", name, t
|
|
|
286
286
|
await writeFile(path.join(target, "README.md"), blankReadme(projectTitle));
|
|
287
287
|
}
|
|
288
288
|
if (app) {
|
|
289
|
+
onProgress?.({ status: "running", step: "application", message: `Importing React application from ${path.resolve(app)}` });
|
|
289
290
|
await installApplication(target, app, projectTitle);
|
|
290
291
|
onProgress?.({ status: "passed", step: "application", message: `Imported React application from ${path.resolve(app)}` });
|
|
291
292
|
}
|
|
293
|
+
onProgress?.({ status: "running", step: "source", message: "Synchronizing React source and report-source contract" });
|
|
292
294
|
const style = await readFile(path.join(target, "source", "report-design.less"), "utf8");
|
|
293
295
|
const designHash = createHash("sha256").update(style).digest("hex");
|
|
294
296
|
await replaceTemplateTokens(target, { DESIGN_HASH: designHash });
|