@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",
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.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",
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.3";
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
- progressStream.write(`\r${" ".repeat(activeProgress.width)}\r`);
64
+ writeProgress(`\r${" ".repeat(activeProgress.width)}\r`);
56
65
  activeProgress = null;
57
66
  }
58
67
 
59
- export function writeInitProgress(event) {
68
+ export function writeCommandProgress(event) {
60
69
  if (event.status === "running") {
61
70
  stopActiveProgress();
62
- progressStream.write(`> ${event.message}\n`);
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
- progressStream.write(`\r${line}`);
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
- progressStream.write(`${marker} ${event.message}\n`);
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) progressStream.write(`\nSuper Visual init\n\n`);
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({ client, workspaceId: options.workspaceId, workspaceName: options.workspaceName, semanticModelId: options.semanticModelId });
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) writeInitProgress({ status: "passed", message: `Verified workspace ${inspected.workspace.displayName} and semantic model ${inspected.semanticModel.displayName}` });
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
- progressStream.write("! Live Fabric validation skipped by request\n");
374
+ writeProgress("! Live Fabric validation skipped by request\n");
353
375
  } else if (!json) {
354
- progressStream.write("! No complete Fabric binding supplied; live validation was not run\n");
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 : writeInitProgress,
385
+ onProgress: json ? null : writeCommandProgress,
364
386
  });
365
387
  if (options.workspaceId) {
366
- if (!json) writeInitProgress({ status: "running", message: "Saving the reusable Fabric project binding" });
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) writeInitProgress({ status: "passed", message: `Saved Fabric binding to ${saved.file}` });
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
- await syncProjectSource(projectFile(project));
424
- const loaded = await loadProject(projectFile(project));
425
- const target = await resolveProjectTarget(loaded.root, options, { requireSemanticModel: true });
426
- const client = new FabricHttpClient({ timeoutSeconds: Number(options.timeout) });
427
- const preview = await startSemanticPreview({
428
- client,
429
- projectFile: projectFile(project),
430
- workspaceId: target.workspaceId,
431
- workspaceName: target.workspaceName,
432
- semanticModelId: target.semanticModelId ?? target.managedSemanticModelId,
433
- host: options.host,
434
- port: Number(options.port),
435
- open: Boolean(options.open),
436
- });
437
- const { close, ...result } = preview;
438
- output(result, jsonEnabled(command));
439
- await waitForPreviewShutdown({ close });
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");
@@ -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
- const inspected = await inspectSemanticModel({ client, workspaceId, workspaceName, semanticModelId });
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: dependencyAliases(), dedupe: ["react", "react-dom"] },
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: `http://${urlHost}:${actualPort}/`,
460
+ url,
449
461
  workspace: inspected.workspace,
450
462
  semanticModel: { id: inspected.semanticModel.id, displayName: inspected.semanticModel.displayName },
451
463
  compatibility: compatibility.summary,
@@ -10,6 +10,6 @@
10
10
  "verify": "supervisual verify ."
11
11
  },
12
12
  "devDependencies": {
13
- "@datashow/super-visual-cli": "0.1.0-rc.3"
13
+ "@datashow/super-visual-cli": "0.1.0-rc.5"
14
14
  }
15
15
  }
@@ -10,6 +10,6 @@
10
10
  "verify": "supervisual verify ."
11
11
  },
12
12
  "devDependencies": {
13
- "@datashow/super-visual-cli": "0.1.0-rc.3"
13
+ "@datashow/super-visual-cli": "0.1.0-rc.5"
14
14
  }
15
15
  }