@datashow/super-visual-cli 0.1.0-rc.3 → 0.1.0-rc.5
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/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.5",
|
|
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.5",
|
|
29
|
+
"@datashow/super-visual-compiler": "0.1.0-rc.5",
|
|
30
|
+
"@datashow/super-visual-contracts": "0.1.0-rc.5",
|
|
31
|
+
"@datashow/super-visual-fabric": "0.1.0-rc.5",
|
|
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
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { cp, mkdir, mkdtemp, readFile, readdir, rename, writeFile } from "node:fs/promises";
|
|
2
|
+
import { writeSync } from "node:fs";
|
|
2
3
|
import { execFile } from "node:child_process";
|
|
3
4
|
import os from "node:os";
|
|
4
5
|
import path from "node:path";
|
|
@@ -15,12 +16,20 @@ import { resolveProjectTarget, targetUpdate, writeProjectTarget } from "./projec
|
|
|
15
16
|
import { runSemanticPreviewTest, startSemanticPreview } from "./semantic-preview.mjs";
|
|
16
17
|
|
|
17
18
|
const run = promisify(execFile);
|
|
18
|
-
const VERSION = "0.1.0-rc.
|
|
19
|
+
const VERSION = "0.1.0-rc.5";
|
|
19
20
|
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
21
|
const SPINNER_FRAMES = ["|", "/", "-", "\\"];
|
|
21
22
|
const progressStream = process.stderr;
|
|
22
23
|
let activeProgress = null;
|
|
23
24
|
|
|
25
|
+
function writeProgress(value) {
|
|
26
|
+
try {
|
|
27
|
+
writeSync(2, value);
|
|
28
|
+
} catch {
|
|
29
|
+
progressStream.write(value);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
24
33
|
export class CliUserError extends Error {
|
|
25
34
|
constructor(message, { summary, guidance } = {}) {
|
|
26
35
|
super(message);
|
|
@@ -52,19 +61,19 @@ function output(value, json) {
|
|
|
52
61
|
export function stopActiveProgress() {
|
|
53
62
|
if (!activeProgress) return;
|
|
54
63
|
clearInterval(activeProgress.timer);
|
|
55
|
-
|
|
64
|
+
writeProgress(`\r${" ".repeat(activeProgress.width)}\r`);
|
|
56
65
|
activeProgress = null;
|
|
57
66
|
}
|
|
58
67
|
|
|
59
|
-
export function
|
|
68
|
+
export function writeCommandProgress(event) {
|
|
60
69
|
if (event.status === "running") {
|
|
61
70
|
stopActiveProgress();
|
|
62
|
-
|
|
71
|
+
writeProgress(`> ${event.message}\n`);
|
|
63
72
|
if (!progressStream.isTTY) return;
|
|
64
73
|
let frame = 0;
|
|
65
74
|
const render = () => {
|
|
66
75
|
const line = `${SPINNER_FRAMES[frame % SPINNER_FRAMES.length]} Working...`;
|
|
67
|
-
|
|
76
|
+
writeProgress(`\r${line}`);
|
|
68
77
|
activeProgress.width = Math.max(activeProgress.width, line.length);
|
|
69
78
|
frame += 1;
|
|
70
79
|
};
|
|
@@ -75,7 +84,15 @@ export function writeInitProgress(event) {
|
|
|
75
84
|
}
|
|
76
85
|
stopActiveProgress();
|
|
77
86
|
const marker = event.status === "passed" ? "✓" : "!";
|
|
78
|
-
|
|
87
|
+
writeProgress(`${marker} ${event.message}\n`);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function printPreviewResult(result) {
|
|
91
|
+
process.stdout.write(`\nLocal preview is ready.\n`);
|
|
92
|
+
process.stdout.write(`URL: ${result.url}\n`);
|
|
93
|
+
process.stdout.write(`Workspace: ${result.workspace.displayName} (${result.workspace.id})\n`);
|
|
94
|
+
process.stdout.write(`Semantic model: ${result.semanticModel.displayName} (${result.semanticModel.id})\n`);
|
|
95
|
+
process.stdout.write("The preview is using live semantic-model data. Press Ctrl+C to stop it.\n");
|
|
79
96
|
}
|
|
80
97
|
|
|
81
98
|
function semanticInventory(model) {
|
|
@@ -325,14 +342,19 @@ function createProgram() {
|
|
|
325
342
|
validateFabricId(options.workspaceId, "--workspace-id");
|
|
326
343
|
validateFabricId(options.semanticModelId, "--semantic-model-id");
|
|
327
344
|
const json = jsonEnabled(command);
|
|
328
|
-
if (!json)
|
|
345
|
+
if (!json) writeProgress(`\nSuper Visual init\n\n`);
|
|
329
346
|
let liveValidation = null;
|
|
330
347
|
if (options.workspaceId && options.semanticModelId && !options.skipLiveValidation) {
|
|
331
|
-
if (!json) writeInitProgress({ status: "running", message: "Verifying the Fabric workspace and Power BI semantic model (read-only)" });
|
|
332
348
|
let inspected;
|
|
333
349
|
try {
|
|
334
350
|
const client = new FabricHttpClient({ timeoutSeconds: Number(options.timeout) });
|
|
335
|
-
inspected = await inspectSemanticModel({
|
|
351
|
+
inspected = await inspectSemanticModel({
|
|
352
|
+
client,
|
|
353
|
+
workspaceId: options.workspaceId,
|
|
354
|
+
workspaceName: options.workspaceName,
|
|
355
|
+
semanticModelId: options.semanticModelId,
|
|
356
|
+
onProgress: json ? null : writeCommandProgress,
|
|
357
|
+
});
|
|
336
358
|
} catch (error) {
|
|
337
359
|
throw new CliUserError(error instanceof Error ? error.message : String(error), {
|
|
338
360
|
summary: "Super Visual could not verify the Fabric workspace and semantic model.",
|
|
@@ -347,11 +369,11 @@ function createProgram() {
|
|
|
347
369
|
semanticModel: { id: inspected.semanticModel.id, displayName: inspected.semanticModel.displayName, format: inspected.semanticModel.format },
|
|
348
370
|
inventory: semanticInventory(inspected.semanticModel),
|
|
349
371
|
};
|
|
350
|
-
if (!json)
|
|
372
|
+
if (!json) writeCommandProgress({ status: "passed", message: `Verified workspace ${inspected.workspace.displayName} and semantic model ${inspected.semanticModel.displayName}` });
|
|
351
373
|
} else if (!json && options.skipLiveValidation) {
|
|
352
|
-
|
|
374
|
+
writeProgress("! Live Fabric validation skipped by request\n");
|
|
353
375
|
} else if (!json) {
|
|
354
|
-
|
|
376
|
+
writeProgress("! No complete Fabric binding supplied; live validation was not run\n");
|
|
355
377
|
}
|
|
356
378
|
const result = await initializeProject({
|
|
357
379
|
directory,
|
|
@@ -360,10 +382,10 @@ function createProgram() {
|
|
|
360
382
|
title: options.title,
|
|
361
383
|
app: options.app,
|
|
362
384
|
force: options.force,
|
|
363
|
-
onProgress: json ? null :
|
|
385
|
+
onProgress: json ? null : writeCommandProgress,
|
|
364
386
|
});
|
|
365
387
|
if (options.workspaceId) {
|
|
366
|
-
if (!json)
|
|
388
|
+
if (!json) writeCommandProgress({ status: "running", message: "Saving the reusable Fabric project binding" });
|
|
367
389
|
const saved = await writeProjectTarget(result.directory, targetUpdate({
|
|
368
390
|
...options,
|
|
369
391
|
workspaceName: options.workspaceName ?? liveValidation?.workspace.displayName,
|
|
@@ -371,7 +393,7 @@ function createProgram() {
|
|
|
371
393
|
reportName: options.reportName ?? result.application.title,
|
|
372
394
|
}));
|
|
373
395
|
result.binding = { file: saved.file, ...saved.value };
|
|
374
|
-
if (!json)
|
|
396
|
+
if (!json) writeCommandProgress({ status: "passed", message: `Saved Fabric binding to ${saved.file}` });
|
|
375
397
|
}
|
|
376
398
|
result.validation = { local: result.validation, live: liveValidation ?? { ok: true, status: "not-run", scope: "fabric-read-only" } };
|
|
377
399
|
if (json) output(result, true);
|
|
@@ -420,23 +442,38 @@ function createProgram() {
|
|
|
420
442
|
.option("--open", "open the preview in the default browser")
|
|
421
443
|
.option("--timeout <seconds>", "Fabric operation timeout", "900")
|
|
422
444
|
.action(async (project = ".", options, command) => {
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
445
|
+
const json = jsonEnabled(command);
|
|
446
|
+
if (!json) writeProgress(`\nSuper Visual dev\n\n`);
|
|
447
|
+
try {
|
|
448
|
+
if (!json) writeCommandProgress({ status: "running", message: "Synchronizing the React source and report contract" });
|
|
449
|
+
await syncProjectSource(projectFile(project));
|
|
450
|
+
if (!json) writeCommandProgress({ status: "passed", message: "Synchronized the React source and report contract" });
|
|
451
|
+
if (!json) writeCommandProgress({ status: "running", message: "Loading the saved Fabric project binding" });
|
|
452
|
+
const loaded = await loadProject(projectFile(project));
|
|
453
|
+
const target = await resolveProjectTarget(loaded.root, options, { requireSemanticModel: true });
|
|
454
|
+
if (!json) writeCommandProgress({ status: "passed", message: "Loaded the Fabric workspace and semantic-model binding" });
|
|
455
|
+
const client = new FabricHttpClient({ timeoutSeconds: Number(options.timeout) });
|
|
456
|
+
const preview = await startSemanticPreview({
|
|
457
|
+
client,
|
|
458
|
+
projectFile: projectFile(project),
|
|
459
|
+
workspaceId: target.workspaceId,
|
|
460
|
+
workspaceName: target.workspaceName,
|
|
461
|
+
semanticModelId: target.semanticModelId ?? target.managedSemanticModelId,
|
|
462
|
+
host: options.host,
|
|
463
|
+
port: Number(options.port),
|
|
464
|
+
open: Boolean(options.open),
|
|
465
|
+
onProgress: json ? null : writeCommandProgress,
|
|
466
|
+
});
|
|
467
|
+
const { close, ...result } = preview;
|
|
468
|
+
if (json) output(result, true);
|
|
469
|
+
else printPreviewResult(result);
|
|
470
|
+
await waitForPreviewShutdown({ close });
|
|
471
|
+
} catch (error) {
|
|
472
|
+
throw new CliUserError(error instanceof Error ? error.message : String(error), {
|
|
473
|
+
summary: "Super Visual could not start the local semantic preview.",
|
|
474
|
+
guidance: "Check the project binding and your Fabric access, then run supervisual dev --open again.",
|
|
475
|
+
});
|
|
476
|
+
}
|
|
440
477
|
});
|
|
441
478
|
|
|
442
479
|
const preview = program.command("preview").description("exercise the local React host against a live semantic model");
|
package/src/semantic-preview.mjs
CHANGED
|
@@ -396,15 +396,18 @@ async function writePreviewHost(root, token) {
|
|
|
396
396
|
await writeFile(path.join(root, "src", "config.ts"), `export const PREVIEW_TOKEN = ${JSON.stringify(token)};\n`);
|
|
397
397
|
}
|
|
398
398
|
|
|
399
|
-
export async function startSemanticPreview({ client, projectFile, workspaceId, workspaceName, semanticModelId, host = "127.0.0.1", port = 5173, open = false }) {
|
|
399
|
+
export async function startSemanticPreview({ client, projectFile, workspaceId, workspaceName, semanticModelId, host = "127.0.0.1", port = 5173, open = false, onProgress = null }) {
|
|
400
400
|
if (!["127.0.0.1", "localhost", "::1"].includes(host)) throw new Error("Semantic preview may bind only to a loopback host");
|
|
401
|
+
onProgress?.({ status: "running", step: "project", message: "Loading and validating the Super Visual project" });
|
|
401
402
|
const loaded = await loadProject(projectFile);
|
|
402
403
|
const diagnostics = validateCompilation(loaded);
|
|
403
404
|
if (diagnostics.length) throw new Error(`Preview validation failed:\n- ${diagnostics.join("\n- ")}`);
|
|
404
|
-
|
|
405
|
+
onProgress?.({ status: "passed", step: "project", message: "Validated the project contracts and semantic bindings" });
|
|
406
|
+
const inspected = await inspectSemanticModel({ client, workspaceId, workspaceName, semanticModelId, onProgress });
|
|
405
407
|
const catalogMode = usesSemanticCatalog(loaded.broker);
|
|
406
408
|
const brokerDiagnostics = previewBrokerSurfaceDiagnostics(inspected.semanticModel);
|
|
407
409
|
const previewMode = catalogMode ? "catalog" : brokerDiagnostics.length ? "direct" : "managed";
|
|
410
|
+
onProgress?.({ status: "running", step: "compatibility", message: "Checking application queries against the selected semantic model" });
|
|
408
411
|
const compatibility = catalogMode
|
|
409
412
|
? { ok: true, summary: { required: 1, matched: 1, missing: 0, ambiguous: 0 }, checks: [], diagnostics: [] }
|
|
410
413
|
: checkSemanticCompatibility(inspected.semanticModel, loaded.semanticModel, loaded.broker);
|
|
@@ -412,19 +415,26 @@ export async function startSemanticPreview({ client, projectFile, workspaceId, w
|
|
|
412
415
|
const messages = compatibility.diagnostics.map((item) => item.message);
|
|
413
416
|
throw new Error(`Semantic preview compatibility failed:\n- ${messages.join("\n- ")}`);
|
|
414
417
|
}
|
|
418
|
+
onProgress?.({ status: "passed", step: "compatibility", message: `Confirmed ${compatibility.summary.matched || compatibility.summary.required} semantic binding${compatibility.summary.required === 1 ? "" : "s"}` });
|
|
415
419
|
loaded.previewMode = previewMode;
|
|
416
420
|
if (catalogMode) loaded.catalogRows = semanticCatalogRows(inspected.semanticModel);
|
|
417
421
|
|
|
418
422
|
const previewRoot = path.join(loaded.root, ".supervisual", "semantic-preview");
|
|
419
423
|
const solutionRoot = path.join(previewRoot, "solution");
|
|
420
424
|
const hostRoot = path.join(previewRoot, "host");
|
|
425
|
+
onProgress?.({ status: "running", step: "build", message: "Building the local preview runtime" });
|
|
421
426
|
await buildSolution(projectFile, { outputDirectory: solutionRoot });
|
|
427
|
+
onProgress?.({ status: "passed", step: "build", message: "Built the local preview runtime" });
|
|
428
|
+
onProgress?.({ status: "running", step: "host", message: "Preparing the secure loopback preview host" });
|
|
422
429
|
const token = randomBytes(24).toString("base64url");
|
|
423
430
|
await writePreviewHost(hostRoot, token);
|
|
431
|
+
const aliases = dependencyAliases();
|
|
432
|
+
onProgress?.({ status: "passed", step: "host", message: "Prepared the secure loopback preview host" });
|
|
433
|
+
onProgress?.({ status: "running", step: "server", message: `Starting the preview server on ${host}:${port}` });
|
|
424
434
|
const server = await createServer({
|
|
425
435
|
root: hostRoot,
|
|
426
436
|
logLevel: "silent",
|
|
427
|
-
resolve: { alias:
|
|
437
|
+
resolve: { alias: aliases, dedupe: ["react", "react-dom"] },
|
|
428
438
|
server: {
|
|
429
439
|
host,
|
|
430
440
|
port: Number(port),
|
|
@@ -441,11 +451,13 @@ export async function startSemanticPreview({ client, projectFile, workspaceId, w
|
|
|
441
451
|
const address = server.httpServer?.address();
|
|
442
452
|
const actualPort = typeof address === "object" && address ? address.port : Number(port);
|
|
443
453
|
const urlHost = host === "::1" ? "[::1]" : host;
|
|
454
|
+
const url = `http://${urlHost}:${actualPort}/`;
|
|
455
|
+
onProgress?.({ status: "passed", step: "server", message: `Preview server is ready at ${url}` });
|
|
444
456
|
return {
|
|
445
457
|
ok: true,
|
|
446
458
|
status: "ready",
|
|
447
459
|
mode: "live-semantic-model",
|
|
448
|
-
url
|
|
460
|
+
url,
|
|
449
461
|
workspace: inspected.workspace,
|
|
450
462
|
semanticModel: { id: inspected.semanticModel.id, displayName: inspected.semanticModel.displayName },
|
|
451
463
|
compatibility: compatibility.summary,
|