@datashow/super-visual-cli 0.1.0-rc.0 → 0.1.0-rc.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.
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.1",
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.1",
29
+ "@datashow/super-visual-compiler": "0.1.0-rc.1",
30
+ "@datashow/super-visual-contracts": "0.1.0-rc.1",
31
+ "@datashow/super-visual-fabric": "0.1.0-rc.1",
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,7 @@ 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.1";
19
19
 
20
20
  function projectFile(input = ".") {
21
21
  const resolved = path.resolve(input);
@@ -26,6 +26,36 @@ function output(value, json) {
26
26
  process.stdout.write(`${json ? JSON.stringify(value) : typeof value === "string" ? value : JSON.stringify(value, null, 2)}\n`);
27
27
  }
28
28
 
29
+ function writeInitProgress(event) {
30
+ const marker = event.status === "passed" ? "✓" : "…";
31
+ process.stdout.write(`${marker} ${event.message}\n`);
32
+ }
33
+
34
+ function semanticInventory(model) {
35
+ const tables = model.tables ?? [];
36
+ return {
37
+ tables: tables.length,
38
+ columns: tables.reduce((total, table) => total + (table.columns?.length ?? 0), 0),
39
+ measures: tables.reduce((total, table) => total + (table.measures?.length ?? 0), 0),
40
+ hierarchies: tables.reduce((total, table) => total + (table.hierarchies?.length ?? 0), 0),
41
+ hierarchyLevels: tables.reduce((total, table) => total + (table.hierarchies ?? []).reduce((levels, hierarchy) => levels + (hierarchy.levels?.length ?? 0), 0), 0),
42
+ relationships: model.relationships?.length ?? 0,
43
+ parameters: model.parameters?.length ?? 0,
44
+ };
45
+ }
46
+
47
+ function printInitializationResult(result) {
48
+ process.stdout.write(`\nInitialization complete — ${result.application.title} is ready.\n`);
49
+ process.stdout.write(`Project: ${result.directory}\n`);
50
+ process.stdout.write(`Project manifest: ${result.projectFile}\n`);
51
+ if (result.binding) {
52
+ if (result.binding.workspace) process.stdout.write(`Workspace: ${result.binding.workspace.name ?? "(name not recorded)"} (${result.binding.workspace.id})\n`);
53
+ if (result.binding.semanticModel) process.stdout.write(`Semantic model: ${result.binding.semanticModel.name ?? "(name not recorded)"} (${result.binding.semanticModel.id})\n`);
54
+ }
55
+ process.stdout.write(`\nInitialization manifest (JSON):\n${JSON.stringify(result, null, 2)}\n`);
56
+ process.stdout.write(`\nNext:\n${result.next.map((command) => ` ${command}`).join("\n")}\n`);
57
+ }
58
+
29
59
  function jsonEnabled(command) {
30
60
  return Boolean(command.optsWithGlobals().json);
31
61
  }
@@ -240,18 +270,54 @@ function createProgram() {
240
270
  .option("--report-name <name>", "target report name")
241
271
  .option("--connection-id <id>", "advanced override for the selected model's Fabric connection")
242
272
  .option("--connection-mode <mode>", "advanced Fabric connectivity override", "ShareableCloud")
273
+ .option("--skip-live-validation", "save the binding without checking the workspace and semantic model")
274
+ .option("--timeout <seconds>", "Fabric validation timeout", "900")
243
275
  .option("--force", "write into an existing empty directory")
244
276
  .action(async (directory, options, command) => {
245
277
  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 });
278
+ const json = jsonEnabled(command);
279
+ if (!json) process.stdout.write(`\nSuper Visual init\n\n`);
280
+ let liveValidation = null;
281
+ if (options.workspaceId && options.semanticModelId && !options.skipLiveValidation) {
282
+ if (!json) writeInitProgress({ status: "running", message: "Verifying the Fabric workspace and Power BI semantic model (read-only)" });
283
+ const client = new FabricHttpClient({ timeoutSeconds: Number(options.timeout) });
284
+ const inspected = await inspectSemanticModel({ client, workspaceId: options.workspaceId, workspaceName: options.workspaceName, semanticModelId: options.semanticModelId });
285
+ liveValidation = {
286
+ ok: true,
287
+ status: "passed",
288
+ scope: "fabric-read-only",
289
+ workspace: inspected.workspace,
290
+ semanticModel: { id: inspected.semanticModel.id, displayName: inspected.semanticModel.displayName, format: inspected.semanticModel.format },
291
+ inventory: semanticInventory(inspected.semanticModel),
292
+ };
293
+ if (!json) writeInitProgress({ status: "passed", message: `Verified workspace ${inspected.workspace.displayName} and semantic model ${inspected.semanticModel.displayName}` });
294
+ } else if (!json && options.skipLiveValidation) {
295
+ process.stdout.write("! Live Fabric validation skipped by request\n");
296
+ } else if (!json) {
297
+ process.stdout.write("• No complete Fabric binding supplied; live validation was not run\n");
298
+ }
299
+ const result = await initializeProject({
300
+ directory,
301
+ template: options.template,
302
+ name: options.name,
303
+ title: options.title,
304
+ app: options.app,
305
+ force: options.force,
306
+ onProgress: json ? null : writeInitProgress,
307
+ });
247
308
  if (options.workspaceId) {
248
309
  const saved = await writeProjectTarget(result.directory, targetUpdate({
249
310
  ...options,
311
+ workspaceName: options.workspaceName ?? liveValidation?.workspace.displayName,
312
+ semanticModelName: liveValidation?.semanticModel.displayName,
250
313
  reportName: options.reportName ?? result.application.title,
251
314
  }));
252
315
  result.binding = { file: saved.file, ...saved.value };
316
+ if (!json) writeInitProgress({ status: "passed", message: `Saved Fabric binding to ${saved.file}` });
253
317
  }
254
- output(result, jsonEnabled(command));
318
+ result.validation = { local: result.validation, live: liveValidation ?? { ok: true, status: "not-run", scope: "fabric-read-only" } };
319
+ if (json) output(result, true);
320
+ else printInitializationResult(result);
255
321
  });
256
322
 
257
323
  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.1"
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.1"
14
14
  }
15
15
  }