@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 +5 -5
- package/src/main.mjs +69 -3
- package/src/project.mjs +21 -3
- package/templates/blank/package.json +1 -1
- package/templates/hello-world/package.json +1 -1
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.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.
|
|
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.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.
|
|
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
|
|
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
|
-
|
|
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)
|
|
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
|
-
|
|
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
|
}
|