@luquimbo/bi-superpowers 4.1.1 → 4.1.3
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/.claude-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +1 -1
- package/.claude-plugin/skill-manifest.json +1 -1
- package/.plugin/plugin.json +1 -1
- package/AGENTS.md +4 -5
- package/CHANGELOG.md +24 -0
- package/README.md +633 -93
- package/bin/cli.js +52 -54
- package/bin/commands/build-desktop.js +60 -6
- package/bin/commands/diff.js +86 -1
- package/bin/commands/watch.js +50 -5
- package/bin/lib/generators/claude-plugin.js +1 -1
- package/bin/postinstall.js +1 -1
- package/commands/bi-start.md +2 -2
- package/commands/pbi-connect.md +1 -1
- package/commands/project-kickoff.md +5 -5
- package/commands/report-design.md +8 -8
- package/desktop-extension/server.js +43 -10
- package/package.json +5 -5
- package/skills/bi-start/SKILL.md +3 -3
- package/skills/bi-start/scripts/update-check.js +1 -1
- package/skills/pbi-connect/SKILL.md +2 -2
- package/skills/pbi-connect/scripts/update-check.js +1 -1
- package/skills/project-kickoff/SKILL.md +6 -6
- package/skills/project-kickoff/scripts/update-check.js +1 -1
- package/skills/report-design/SKILL.md +9 -9
- package/skills/report-design/references/layouts/finance.md +2 -2
- package/skills/report-design/references/native-visuals.md +2 -2
- package/skills/report-design/references/slicer.md +1 -1
- package/skills/report-design/references/textbox.md +1 -1
- package/skills/report-design/scripts/create-visual.js +65 -1
- package/skills/report-design/scripts/update-check.js +1 -1
- package/skills/report-design/scripts/validate-pbir.js +29 -0
- package/src/content/skills/bi-start.md +2 -2
- package/src/content/skills/project-kickoff.md +4 -4
- package/src/content/skills/report-design/SKILL.md +7 -7
- package/src/content/skills/report-design/references/layouts/finance.md +2 -2
- package/src/content/skills/report-design/references/native-visuals.md +2 -2
- package/src/content/skills/report-design/references/slicer.md +1 -1
- package/src/content/skills/report-design/references/textbox.md +1 -1
- package/src/content/skills/report-design/scripts/create-visual.js +65 -1
- package/src/content/skills/report-design/scripts/validate-pbir.js +29 -0
|
@@ -311,7 +311,7 @@ Si PBI Desktop introduce un visualType nuevo (o encontrás uno nativo que falta
|
|
|
311
311
|
|
|
312
312
|
## Features de `visual.json` que `create-visual.js` NO soporta todavía
|
|
313
313
|
|
|
314
|
-
Descubiertos al regenerar la smoke-test "Galería de Visuales" end-to-end con el script
|
|
314
|
+
Descubiertos al regenerar la smoke-test "Galería de Visuales" end-to-end con el script. No bloquean el authoring — la shape que emite el script pasa `pbi report validate` y `validate-pbir.js` — pero sí dejan regresiones visuales si el `visual.json` hand-written aprovechaba alguno de estos features:
|
|
315
315
|
|
|
316
316
|
1. **`query.sortDefinition`** — sort explícito por medida/columna con dirección. `create-visual.js` no lo emite, así que un visual "ranking" tipo barChart ordenado descendente por una medida pierde el orden en la regeneración. **Workaround**: ordenar en Desktop manualmente después del `.pbip` abrir, o hand-extender el `visual.json` con `sortDefinition` post-creación. **Fix futuro**: `--sort-by "Role:Field" --sort-dir descending` en `create-visual.js`.
|
|
317
317
|
|
|
@@ -319,7 +319,7 @@ Descubiertos al regenerar la smoke-test "Galería de Visuales" end-to-end con el
|
|
|
319
319
|
|
|
320
320
|
3. **`queryState.<Role>.projections[].active`** — las columnas siempre salen con `active: true`, las medidas nunca. Si un visual hand-written tenía `active: true` en una medida (para indicar que la medida es la "default Y") o `active: false` en una columna (hidden projection), el script normaliza. Rara vez significativo para el render pero cambia el shape JSON.
|
|
321
321
|
|
|
322
|
-
4. **Formato condicional**: colores signados (positivo verde / negativo rojo), gradientes en matriz, data bars en tabla. Ninguno de estos expresa shape via `create-visual.js
|
|
322
|
+
4. **Formato condicional**: colores signados (positivo verde / negativo rojo), gradientes en matriz, data bars en tabla. Ninguno de estos expresa shape via `create-visual.js`; tratarlos como diseño previsto hasta que exista una implementación PBIR testeada.
|
|
323
323
|
|
|
324
324
|
Cualquier cambio en el script que cubra estos gaps debe: (a) agregar el flag a `parseArgs`, (b) cubrirlo con tests en `create-visual.test.js`, (c) documentarlo en este archivo, (d) regenerar la Galería para que ejercite el feature.
|
|
325
325
|
|
|
@@ -86,4 +86,4 @@ cat > "<reportPath>/definition/pages/<pageName>/visuals/<visualName>/visual.json
|
|
|
86
86
|
EOF
|
|
87
87
|
```
|
|
88
88
|
|
|
89
|
-
|
|
89
|
+
Prefer `scripts/create-visual.js --type slicer` for new slicers. Keep the heredoc pattern above only as a fallback when the helper script is unavailable.
|
|
@@ -98,4 +98,4 @@ cat > "<reportPath>/definition/pages/<pageName>/visuals/<visualName>/visual.json
|
|
|
98
98
|
EOF
|
|
99
99
|
```
|
|
100
100
|
|
|
101
|
-
|
|
101
|
+
Prefer `scripts/create-visual.js --type textbox` for new textboxes. Keep the heredoc pattern above only as a fallback when the helper script is unavailable.
|
|
@@ -679,6 +679,66 @@ function nextInt(existing, key) {
|
|
|
679
679
|
return max + 1;
|
|
680
680
|
}
|
|
681
681
|
|
|
682
|
+
function validateVisualName(name) {
|
|
683
|
+
if (typeof name !== 'string' || name.trim() === '') {
|
|
684
|
+
fail('--name must be a non-empty visual folder name');
|
|
685
|
+
}
|
|
686
|
+
|
|
687
|
+
if (name !== name.trim()) {
|
|
688
|
+
fail(`--name must not have leading or trailing whitespace (got: ${JSON.stringify(name)})`);
|
|
689
|
+
}
|
|
690
|
+
|
|
691
|
+
if (
|
|
692
|
+
name === '.' ||
|
|
693
|
+
name === '..' ||
|
|
694
|
+
/[\\/]/.test(name) ||
|
|
695
|
+
path.basename(name) !== name ||
|
|
696
|
+
path.win32.basename(name) !== name ||
|
|
697
|
+
path.posix.basename(name) !== name
|
|
698
|
+
) {
|
|
699
|
+
fail(`--name must be a single visual folder name, not a path (got: ${JSON.stringify(name)})`);
|
|
700
|
+
}
|
|
701
|
+
|
|
702
|
+
if (/[\u0000-\u001f<>:"|?*]/.test(name)) {
|
|
703
|
+
fail(`--name contains characters that are not valid in a Windows folder name (got: ${JSON.stringify(name)})`);
|
|
704
|
+
}
|
|
705
|
+
}
|
|
706
|
+
|
|
707
|
+
function validateNumericOptions(args) {
|
|
708
|
+
const numericOptions = [
|
|
709
|
+
['x', '--x'],
|
|
710
|
+
['y', '--y'],
|
|
711
|
+
['z', '-z'],
|
|
712
|
+
['width', '--width'],
|
|
713
|
+
['height', '--height'],
|
|
714
|
+
['tabOrder', '--tab-order'],
|
|
715
|
+
];
|
|
716
|
+
|
|
717
|
+
for (const [key, flag] of numericOptions) {
|
|
718
|
+
if (args[key] != null && !Number.isFinite(args[key])) {
|
|
719
|
+
fail(`${flag} must be a finite number`);
|
|
720
|
+
}
|
|
721
|
+
}
|
|
722
|
+
|
|
723
|
+
for (const [key, flag] of [
|
|
724
|
+
['width', '--width'],
|
|
725
|
+
['height', '--height'],
|
|
726
|
+
]) {
|
|
727
|
+
if (args[key] != null && args[key] <= 0) {
|
|
728
|
+
fail(`${flag} must be greater than 0`);
|
|
729
|
+
}
|
|
730
|
+
}
|
|
731
|
+
}
|
|
732
|
+
|
|
733
|
+
function assertPathInside(childPath, parentPath, label) {
|
|
734
|
+
const parent = path.resolve(parentPath);
|
|
735
|
+
const child = path.resolve(childPath);
|
|
736
|
+
const relative = path.relative(parent, child);
|
|
737
|
+
if (!relative || relative.startsWith('..') || path.isAbsolute(relative)) {
|
|
738
|
+
fail(`${label} resolved outside the expected directory: ${child}`);
|
|
739
|
+
}
|
|
740
|
+
}
|
|
741
|
+
|
|
682
742
|
// ---------------------------------------------------------------------------
|
|
683
743
|
// main()
|
|
684
744
|
// ---------------------------------------------------------------------------
|
|
@@ -698,6 +758,7 @@ function main() {
|
|
|
698
758
|
if (!args.reportPath) fail('--report-path is required');
|
|
699
759
|
if (!args.page) fail('--page is required');
|
|
700
760
|
if (!args.type) fail('--type is required (use --list-types to see options)');
|
|
761
|
+
validateNumericOptions(args);
|
|
701
762
|
|
|
702
763
|
// ---- type validation with friendly errors for known non-native aliases
|
|
703
764
|
if (KNOWN_NON_NATIVE_TYPES[args.type]) {
|
|
@@ -727,8 +788,11 @@ function main() {
|
|
|
727
788
|
} while (existing.some((v) => v.name === candidate));
|
|
728
789
|
name = candidate;
|
|
729
790
|
}
|
|
791
|
+
validateVisualName(name);
|
|
730
792
|
|
|
731
|
-
const
|
|
793
|
+
const visualsDir = path.join(pageDir, 'visuals');
|
|
794
|
+
const visualDir = path.join(visualsDir, name);
|
|
795
|
+
assertPathInside(visualDir, visualsDir, '--name');
|
|
732
796
|
const visualJsonPath = path.join(visualDir, 'visual.json');
|
|
733
797
|
if (fs.existsSync(visualJsonPath)) {
|
|
734
798
|
fail(
|
|
@@ -130,6 +130,33 @@ function boundRoles(visualData) {
|
|
|
130
130
|
return bound;
|
|
131
131
|
}
|
|
132
132
|
|
|
133
|
+
function validatePosition(position) {
|
|
134
|
+
const errors = [];
|
|
135
|
+
const requiredFields = ['x', 'y', 'z', 'height', 'width', 'tabOrder'];
|
|
136
|
+
|
|
137
|
+
if (!position || typeof position !== 'object') {
|
|
138
|
+
return requiredFields.map((field) => ({
|
|
139
|
+
severity: 'error',
|
|
140
|
+
rule: 'position-number',
|
|
141
|
+
field,
|
|
142
|
+
message: `position.${field} must be a finite number`,
|
|
143
|
+
}));
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
for (const field of requiredFields) {
|
|
147
|
+
if (!Number.isFinite(position[field])) {
|
|
148
|
+
errors.push({
|
|
149
|
+
severity: 'error',
|
|
150
|
+
rule: 'position-number',
|
|
151
|
+
field,
|
|
152
|
+
message: `position.${field} must be a finite number`,
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
return errors;
|
|
158
|
+
}
|
|
159
|
+
|
|
133
160
|
function validateVisual(visual) {
|
|
134
161
|
const errors = [];
|
|
135
162
|
const { data, path: filePath } = visual;
|
|
@@ -148,6 +175,8 @@ function validateVisual(visual) {
|
|
|
148
175
|
errors.push({ severity: 'error', rule: 'name', message: 'missing "name"' });
|
|
149
176
|
}
|
|
150
177
|
|
|
178
|
+
errors.push(...validatePosition(data.position));
|
|
179
|
+
|
|
151
180
|
const vt = data.visual && data.visual.visualType;
|
|
152
181
|
if (!vt) {
|
|
153
182
|
errors.push({ severity: 'error', rule: 'visual-type', message: 'missing visualType' });
|