@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.
Files changed (42) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/.claude-plugin/skill-manifest.json +1 -1
  4. package/.plugin/plugin.json +1 -1
  5. package/AGENTS.md +4 -5
  6. package/CHANGELOG.md +24 -0
  7. package/README.md +633 -93
  8. package/bin/cli.js +52 -54
  9. package/bin/commands/build-desktop.js +60 -6
  10. package/bin/commands/diff.js +86 -1
  11. package/bin/commands/watch.js +50 -5
  12. package/bin/lib/generators/claude-plugin.js +1 -1
  13. package/bin/postinstall.js +1 -1
  14. package/commands/bi-start.md +2 -2
  15. package/commands/pbi-connect.md +1 -1
  16. package/commands/project-kickoff.md +5 -5
  17. package/commands/report-design.md +8 -8
  18. package/desktop-extension/server.js +43 -10
  19. package/package.json +5 -5
  20. package/skills/bi-start/SKILL.md +3 -3
  21. package/skills/bi-start/scripts/update-check.js +1 -1
  22. package/skills/pbi-connect/SKILL.md +2 -2
  23. package/skills/pbi-connect/scripts/update-check.js +1 -1
  24. package/skills/project-kickoff/SKILL.md +6 -6
  25. package/skills/project-kickoff/scripts/update-check.js +1 -1
  26. package/skills/report-design/SKILL.md +9 -9
  27. package/skills/report-design/references/layouts/finance.md +2 -2
  28. package/skills/report-design/references/native-visuals.md +2 -2
  29. package/skills/report-design/references/slicer.md +1 -1
  30. package/skills/report-design/references/textbox.md +1 -1
  31. package/skills/report-design/scripts/create-visual.js +65 -1
  32. package/skills/report-design/scripts/update-check.js +1 -1
  33. package/skills/report-design/scripts/validate-pbir.js +29 -0
  34. package/src/content/skills/bi-start.md +2 -2
  35. package/src/content/skills/project-kickoff.md +4 -4
  36. package/src/content/skills/report-design/SKILL.md +7 -7
  37. package/src/content/skills/report-design/references/layouts/finance.md +2 -2
  38. package/src/content/skills/report-design/references/native-visuals.md +2 -2
  39. package/src/content/skills/report-design/references/slicer.md +1 -1
  40. package/src/content/skills/report-design/references/textbox.md +1 -1
  41. package/src/content/skills/report-design/scripts/create-visual.js +65 -1
  42. 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 (backlog item 2, cycle v4.1). 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:
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`. Quedó deferido a v4.1+ en `SKILL.md` PHASE 4.3.
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
- A reusable helper script (`scripts/write-slicer.sh`) is a v4.1 candidate; v4.0.0 ships with the heredoc pattern.
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
- A reusable helper script (`scripts/write-textbox.sh`) is a v4.1 candidate; v4.0.0 ships with the heredoc pattern.
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 visualDir = path.join(pageDir, 'visuals', name);
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' });