@datashow/super-visual-cli 0.1.0-rc.0 → 0.1.0-rc.2

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.
@@ -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
- process.stderr.write(`${json ? JSON.stringify({ ok: false, error: message }) : `supervisual: ${message}`}\n`);
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.0",
3
+ "version": "0.1.0-rc.2",
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.0",
29
- "@datashow/super-visual-compiler": "0.1.0-rc.0",
30
- "@datashow/super-visual-contracts": "0.1.0-rc.0",
31
- "@datashow/super-visual-fabric": "0.1.0-rc.0",
28
+ "@datashow/super-visual-adapter-claude-dashboard": "0.1.0-rc.2",
29
+ "@datashow/super-visual-compiler": "0.1.0-rc.2",
30
+ "@datashow/super-visual-contracts": "0.1.0-rc.2",
31
+ "@datashow/super-visual-fabric": "0.1.0-rc.2",
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,29 @@ 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.0";
18
+ const VERSION = "0.1.0-rc.2";
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
+ let activeProgress = null;
22
+
23
+ export class CliUserError extends Error {
24
+ constructor(message, { summary, guidance } = {}) {
25
+ super(message);
26
+ this.name = "CliUserError";
27
+ this.summary = summary;
28
+ this.guidance = guidance;
29
+ }
30
+ }
31
+
32
+ export function validateFabricId(value, optionName) {
33
+ if (value === undefined) return;
34
+ if (FABRIC_ID_PATTERN.test(value)) return;
35
+ const target = optionName === "--workspace-id" ? "workspace" : "semantic model";
36
+ throw new CliUserError(`${optionName} must be a 36-character GUID in 8-4-4-4-12 format; received "${value}" (${String(value).length} characters).`, {
37
+ summary: `Super Visual could not initialize the project because the ${target} ID is not valid.`,
38
+ guidance: `Copy the complete ${target} ID from Fabric and run the command again.`,
39
+ });
40
+ }
19
41
 
20
42
  function projectFile(input = ".") {
21
43
  const resolved = path.resolve(input);
@@ -26,6 +48,58 @@ function output(value, json) {
26
48
  process.stdout.write(`${json ? JSON.stringify(value) : typeof value === "string" ? value : JSON.stringify(value, null, 2)}\n`);
27
49
  }
28
50
 
51
+ export function stopActiveProgress() {
52
+ if (!activeProgress) return;
53
+ clearInterval(activeProgress.timer);
54
+ process.stdout.write(`\r${" ".repeat(activeProgress.width)}\r`);
55
+ activeProgress = null;
56
+ }
57
+
58
+ function writeInitProgress(event) {
59
+ if (event.status === "running" && process.stdout.isTTY) {
60
+ stopActiveProgress();
61
+ let frame = 0;
62
+ const render = () => {
63
+ const line = `${SPINNER_FRAMES[frame % SPINNER_FRAMES.length]} ${event.message}`;
64
+ process.stdout.write(`\r${line}`);
65
+ activeProgress.width = Math.max(activeProgress.width, line.length);
66
+ frame += 1;
67
+ };
68
+ activeProgress = { timer: null, width: event.message.length + 2 };
69
+ render();
70
+ activeProgress.timer = setInterval(render, 80);
71
+ return;
72
+ }
73
+ stopActiveProgress();
74
+ const marker = event.status === "passed" ? "✓" : "…";
75
+ process.stdout.write(`${marker} ${event.message}\n`);
76
+ }
77
+
78
+ function semanticInventory(model) {
79
+ const tables = model.tables ?? [];
80
+ return {
81
+ tables: tables.length,
82
+ columns: tables.reduce((total, table) => total + (table.columns?.length ?? 0), 0),
83
+ measures: tables.reduce((total, table) => total + (table.measures?.length ?? 0), 0),
84
+ hierarchies: tables.reduce((total, table) => total + (table.hierarchies?.length ?? 0), 0),
85
+ hierarchyLevels: tables.reduce((total, table) => total + (table.hierarchies ?? []).reduce((levels, hierarchy) => levels + (hierarchy.levels?.length ?? 0), 0), 0),
86
+ relationships: model.relationships?.length ?? 0,
87
+ parameters: model.parameters?.length ?? 0,
88
+ };
89
+ }
90
+
91
+ function printInitializationResult(result) {
92
+ process.stdout.write(`\nInitialization complete — ${result.application.title} is ready.\n`);
93
+ process.stdout.write(`Project: ${result.directory}\n`);
94
+ process.stdout.write(`Project manifest: ${result.projectFile}\n`);
95
+ if (result.binding) {
96
+ if (result.binding.workspace) process.stdout.write(`Workspace: ${result.binding.workspace.name ?? "(name not recorded)"} (${result.binding.workspace.id})\n`);
97
+ if (result.binding.semanticModel) process.stdout.write(`Semantic model: ${result.binding.semanticModel.name ?? "(name not recorded)"} (${result.binding.semanticModel.id})\n`);
98
+ }
99
+ process.stdout.write(`\nInitialization manifest (JSON):\n${JSON.stringify(result, null, 2)}\n`);
100
+ process.stdout.write(`\nNext:\n${result.next.map((command) => ` ${command}`).join("\n")}\n`);
101
+ }
102
+
29
103
  function jsonEnabled(command) {
30
104
  return Boolean(command.optsWithGlobals().json);
31
105
  }
@@ -240,18 +314,64 @@ function createProgram() {
240
314
  .option("--report-name <name>", "target report name")
241
315
  .option("--connection-id <id>", "advanced override for the selected model's Fabric connection")
242
316
  .option("--connection-mode <mode>", "advanced Fabric connectivity override", "ShareableCloud")
317
+ .option("--skip-live-validation", "save the binding without checking the workspace and semantic model")
318
+ .option("--timeout <seconds>", "Fabric validation timeout", "900")
243
319
  .option("--force", "write into an existing empty directory")
244
320
  .action(async (directory, options, command) => {
245
321
  if ((options.workspaceName || options.semanticModelId || options.reportName || options.connectionId) && !options.workspaceId) throw new Error("--workspace-id is required when saving a Fabric binding");
246
- const result = await initializeProject({ directory, template: options.template, name: options.name, title: options.title, app: options.app, force: options.force });
322
+ validateFabricId(options.workspaceId, "--workspace-id");
323
+ validateFabricId(options.semanticModelId, "--semantic-model-id");
324
+ const json = jsonEnabled(command);
325
+ if (!json) process.stdout.write(`\nSuper Visual init\n\n`);
326
+ let liveValidation = null;
327
+ if (options.workspaceId && options.semanticModelId && !options.skipLiveValidation) {
328
+ if (!json) writeInitProgress({ status: "running", message: "Verifying the Fabric workspace and Power BI semantic model (read-only)" });
329
+ let inspected;
330
+ try {
331
+ const client = new FabricHttpClient({ timeoutSeconds: Number(options.timeout) });
332
+ inspected = await inspectSemanticModel({ client, workspaceId: options.workspaceId, workspaceName: options.workspaceName, semanticModelId: options.semanticModelId });
333
+ } catch (error) {
334
+ throw new CliUserError(error instanceof Error ? error.message : String(error), {
335
+ summary: "Super Visual could not verify the Fabric workspace and semantic model.",
336
+ guidance: "Confirm that you are signed in, can access the workspace, and copied both IDs from the same Fabric workspace.",
337
+ });
338
+ }
339
+ liveValidation = {
340
+ ok: true,
341
+ status: "passed",
342
+ scope: "fabric-read-only",
343
+ workspace: inspected.workspace,
344
+ semanticModel: { id: inspected.semanticModel.id, displayName: inspected.semanticModel.displayName, format: inspected.semanticModel.format },
345
+ inventory: semanticInventory(inspected.semanticModel),
346
+ };
347
+ if (!json) writeInitProgress({ status: "passed", message: `Verified workspace ${inspected.workspace.displayName} and semantic model ${inspected.semanticModel.displayName}` });
348
+ } else if (!json && options.skipLiveValidation) {
349
+ process.stdout.write("! Live Fabric validation skipped by request\n");
350
+ } else if (!json) {
351
+ process.stdout.write("• No complete Fabric binding supplied; live validation was not run\n");
352
+ }
353
+ const result = await initializeProject({
354
+ directory,
355
+ template: options.template,
356
+ name: options.name,
357
+ title: options.title,
358
+ app: options.app,
359
+ force: options.force,
360
+ onProgress: json ? null : writeInitProgress,
361
+ });
247
362
  if (options.workspaceId) {
248
363
  const saved = await writeProjectTarget(result.directory, targetUpdate({
249
364
  ...options,
365
+ workspaceName: options.workspaceName ?? liveValidation?.workspace.displayName,
366
+ semanticModelName: liveValidation?.semanticModel.displayName,
250
367
  reportName: options.reportName ?? result.application.title,
251
368
  }));
252
369
  result.binding = { file: saved.file, ...saved.value };
370
+ if (!json) writeInitProgress({ status: "passed", message: `Saved Fabric binding to ${saved.file}` });
253
371
  }
254
- output(result, jsonEnabled(command));
372
+ result.validation = { local: result.validation, live: liveValidation ?? { ok: true, status: "not-run", scope: "fabric-read-only" } };
373
+ if (json) output(result, true);
374
+ else printInitializationResult(result);
255
375
  });
256
376
 
257
377
  addJsonOption(program.command("check [project]"))
package/src/project.mjs CHANGED
@@ -7,6 +7,14 @@ import { inspectClaudeDashboard } from "@datashow/super-visual-adapter-claude-da
7
7
 
8
8
  const packageRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
9
9
  const templatesRoot = path.join(packageRoot, "templates");
10
+ const initializationChecks = [
11
+ "project-contract",
12
+ "widget-broker",
13
+ "page-set-primitives",
14
+ "report-graph-bindings",
15
+ "report-source-policy",
16
+ "semantic-references",
17
+ ];
10
18
 
11
19
  async function exists(file) {
12
20
  try { await access(file); return true; } catch { return false; }
@@ -240,9 +248,10 @@ export async function syncProjectSource(projectFile) {
240
248
  return { ok: true, synced: true, files: nextFiles.length, bundlePath, pageSetPath };
241
249
  }
242
250
 
243
- export async function initializeProject({ directory, template = "blank", name, title, app, force = false }) {
251
+ export async function initializeProject({ directory, template = "blank", name, title, app, force = false, onProgress = null }) {
244
252
  if (!supportedTemplates().some((candidate) => candidate.id === template)) throw new Error(`Unknown template ${template}`);
245
253
  const target = path.resolve(directory);
254
+ onProgress?.({ status: "running", step: "scaffold", message: `Creating ${template} project in ${target}` });
246
255
  if (await exists(target)) {
247
256
  const metadata = await stat(target);
248
257
  if (!metadata.isDirectory()) throw new Error(`Target exists and is not a directory: ${target}`);
@@ -261,6 +270,8 @@ export async function initializeProject({ directory, template = "blank", name, t
261
270
  VISUAL_NAME: visualName,
262
271
  VISUAL_GUID: visualGuid,
263
272
  });
273
+ onProgress?.({ status: "passed", step: "scaffold", message: `Created project files from the ${template} template` });
274
+ onProgress?.({ status: "passed", step: "identity", message: `Generated unique visual identity ${visualGuid}` });
264
275
  if (template === "blank") {
265
276
  const entry = blankApplication.replaceAll("{{PROJECT_TITLE}}", projectTitle);
266
277
  await writeFile(path.join(target, "source", "reportApplication.tsx"), entry);
@@ -274,7 +285,10 @@ export async function initializeProject({ directory, template = "blank", name, t
274
285
  await writeFile(bundlePath, `${JSON.stringify(bundle, null, 2)}\n`);
275
286
  await writeFile(path.join(target, "README.md"), blankReadme(projectTitle));
276
287
  }
277
- if (app) await installApplication(target, app, projectTitle);
288
+ if (app) {
289
+ await installApplication(target, app, projectTitle);
290
+ onProgress?.({ status: "passed", step: "application", message: `Imported React application from ${path.resolve(app)}` });
291
+ }
278
292
  const style = await readFile(path.join(target, "source", "report-design.less"), "utf8");
279
293
  const designHash = createHash("sha256").update(style).digest("hex");
280
294
  await replaceTemplateTokens(target, { DESIGN_HASH: designHash });
@@ -283,7 +297,11 @@ export async function initializeProject({ directory, template = "blank", name, t
283
297
  bundle.designSystem.sha256 = designHash;
284
298
  await writeFile(bundlePath, `${JSON.stringify(bundle, null, 2)}\n`);
285
299
  await syncProjectSource(path.join(target, "supervisual.yaml"));
300
+ onProgress?.({ status: "passed", step: "source", message: "Synchronized React source and report-source contract" });
301
+ onProgress?.({ status: "running", step: "validation", message: "Validating generated contracts, bindings, and source policy" });
286
302
  const inspection = await inspectProject(path.join(target, "supervisual.yaml"));
287
303
  if (!inspection.ok) throw new Error(`Generated project did not validate:\n- ${inspection.diagnostics.join("\n- ")}`);
288
- return { ok: true, directory: target, projectFile: path.join(target, "supervisual.yaml"), template, application: inspection.application, ...(app ? { reactApplication: path.resolve(app) } : {}), next: [`cd ${target}`, "supervisual dev", "supervisual publish"] };
304
+ const validation = { ok: true, status: "passed", scope: "local", checks: initializationChecks, diagnostics: inspection.diagnostics };
305
+ onProgress?.({ status: "passed", step: "validation", message: `${initializationChecks.length} local validation checks passed with no diagnostics` });
306
+ return { ok: true, status: "ready", directory: target, projectFile: path.join(target, "supervisual.yaml"), template, application: inspection.application, validation, ...(app ? { reactApplication: path.resolve(app) } : {}), next: [`cd ${target}`, "supervisual dev --open", "supervisual publish"] };
289
307
  }
@@ -10,6 +10,6 @@
10
10
  "verify": "supervisual verify ."
11
11
  },
12
12
  "devDependencies": {
13
- "@datashow/super-visual-cli": "0.1.0-rc.0"
13
+ "@datashow/super-visual-cli": "0.1.0-rc.2"
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.0"
13
+ "@datashow/super-visual-cli": "0.1.0-rc.2"
14
14
  }
15
15
  }