@pacaf/wizard-ux 3.6.7 → 3.6.8

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/dist/index.html CHANGED
@@ -28,7 +28,7 @@
28
28
  }
29
29
  @keyframes spin { to { transform: rotate(360deg); } }
30
30
  </style>
31
- <script type="module" crossorigin src="/assets/index-CKpAviup.js"></script>
31
+ <script type="module" crossorigin src="/assets/index-BId92dEF.js"></script>
32
32
  </head>
33
33
  <body>
34
34
  <div id="root"><div id="boot"><div class="ring"></div></div></div>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pacaf/wizard-ux",
3
- "version": "3.6.7",
3
+ "version": "3.6.8",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "description": "Browser-based setup wizard for Power Apps Code Apps.",
@@ -63,7 +63,7 @@
63
63
  "dev": "node server/index.mjs",
64
64
  "build": "vite build",
65
65
  "preview": "vite preview --port 5174",
66
- "test": "node --test server/steps/05-environments.test.mjs",
66
+ "test": "node --test server/steps/05-environments.test.mjs server/lib/process-runner.test.mjs",
67
67
  "postinstall": "node scripts/fix-pty-perms.mjs"
68
68
  }
69
69
  }
@@ -16,7 +16,7 @@ class Run extends EventEmitter {
16
16
  constructor(id) {
17
17
  super();
18
18
  this.id = id;
19
- this.lines = []; // { stream: 'stdout'|'stderr', text, ts }
19
+ this.lines = []; // { stream: 'stdout'|'stderr', level: 'info'|'warn'|'error', text, ts }
20
20
  this.status = 'pending'; // pending | running | done | error
21
21
  this.exitCode = null;
22
22
  this.error = null;
@@ -25,12 +25,17 @@ class Run extends EventEmitter {
25
25
  this.child = null;
26
26
  }
27
27
 
28
- push(stream, text) {
28
+ push(stream, text, level = 'info') {
29
29
  // Defense-in-depth: scrub any secret-shaped values from text before it
30
30
  // hits the SSE log buffer. The buffer is streamed to the browser and
31
31
  // rendered in the UI; never let a real secret land there.
32
32
  const safe = SCRUB.scrubSecrets(text);
33
- const evt = { stream, text: safe, ts: Date.now() };
33
+ // `level` reflects INTENT, not the OS pipe. Raw subprocess stderr is
34
+ // 'info' by default — git, npm, vitest and pac all write normal progress
35
+ // to stderr, so the pipe alone must never imply a warning. Only the
36
+ // wizard's own log.warn/log.fail set 'warn'/'error'. The UI keys its
37
+ // warning banner and red coloring off `level`, not `stream`.
38
+ const evt = { stream, level, text: safe, ts: Date.now() };
34
39
  this.lines.push(evt);
35
40
  if (this.lines.length > 1000) this.lines.shift();
36
41
  this.emit('line', evt);
@@ -106,8 +111,8 @@ export async function runInline(run, fn) {
106
111
  const log = {
107
112
  info: (msg) => run.push('stdout', `${msg}\n`),
108
113
  ok: (msg) => run.push('stdout', `✓ ${msg}\n`),
109
- warn: (msg) => run.push('stderr', `⚠ ${msg}\n`),
110
- fail: (msg) => run.push('stderr', `✗ ${msg}\n`),
114
+ warn: (msg) => run.push('stderr', `⚠ ${msg}\n`, 'warn'),
115
+ fail: (msg) => run.push('stderr', `✗ ${msg}\n`, 'error'),
111
116
  };
112
117
  try {
113
118
  const result = await fn(log);
@@ -0,0 +1,46 @@
1
+ import test from 'node:test';
2
+ import assert from 'node:assert/strict';
3
+ import { newRun, runInline } from './process-runner.mjs';
4
+
5
+ // The UI decides whether to show the "review items" / agent-triage banner from
6
+ // each log line's `level`, NOT its OS pipe. Raw subprocess stderr (git, npm,
7
+ // vitest, pac all write normal progress to stderr) must stay 'info', or every
8
+ // successful step falsely looks like it finished with warnings.
9
+
10
+ test('raw stderr lines are tagged info, not warn', () => {
11
+ const run = newRun();
12
+ run.push('stderr', 'Cloning into ...\n'); // typical git/npm stderr chatter
13
+ const line = run.lines.at(-1);
14
+ assert.equal(line.stream, 'stderr');
15
+ assert.equal(line.level, 'info');
16
+ });
17
+
18
+ test('raw stdout lines are tagged info', () => {
19
+ const run = newRun();
20
+ run.push('stdout', 'hello\n');
21
+ assert.equal(run.lines.at(-1).level, 'info');
22
+ });
23
+
24
+ test('log.warn raises level to warn; log.fail to error; log.ok/info stay info', async () => {
25
+ const run = newRun();
26
+ await runInline(run, async (log) => {
27
+ log.ok('all good');
28
+ log.info('fyi');
29
+ log.warn('optional thing missing');
30
+ log.fail('something broke');
31
+ });
32
+ const byText = (needle) => run.lines.find((l) => l.text.includes(needle));
33
+ assert.equal(byText('all good').level, 'info');
34
+ assert.equal(byText('fyi').level, 'info');
35
+ assert.equal(byText('optional thing missing').level, 'warn');
36
+ assert.equal(byText('something broke').level, 'error');
37
+ });
38
+
39
+ test('only warn/error lines would trigger the warning banner', async () => {
40
+ const run = newRun();
41
+ // Simulate a successful step that shelled out (stderr chatter) and logged ok.
42
+ run.push('stderr', 'npm notice ...\n');
43
+ await runInline(run, async (log) => { log.ok('done'); });
44
+ const hasWarnings = run.lines.some((l) => l.level === 'warn' || l.level === 'error');
45
+ assert.equal(hasWarnings, false);
46
+ });
@@ -207,8 +207,12 @@ export default {
207
207
  // Note: The Dataverse-skills plugin manages its own Python SDK installation
208
208
  // via the dv-connect skill. No separate pip install needed here.
209
209
 
210
- // Dataverse Python SDK (PowerPlatform-Dataverse-Client + pandas) — warning only.
211
- // dv-connect can install this, so it does not block, but we surface it.
210
+ // Dataverse Python SDK (PowerPlatform-Dataverse-Client + pandas) — purely
211
+ // informational. It is NOT needed for scaffold/build/deploy, and the
212
+ // Dataverse-skills plugin installs it on demand via dv-connect. Emit at
213
+ // info level (not warn) so its absence never trips the step's warning /
214
+ // triage banner — telling the user to pip-install something they don't
215
+ // need yet is exactly the noise we want to avoid.
212
216
  const sdkOk = detectDataverseSdk(pythonCmd);
213
217
  if (sdkOk) {
214
218
  checks.push({ name: 'Dataverse Python SDK', ok: true, value: 'available', hint: null });
@@ -218,10 +222,10 @@ export default {
218
222
  name: 'Dataverse Python SDK',
219
223
  ok: false,
220
224
  value: null,
221
- hint: 'Run: pip install PowerPlatform-Dataverse-Client pandas',
225
+ hint: 'Optional — dv-connect installs it when you start Dataverse work (pip install PowerPlatform-Dataverse-Client pandas)',
222
226
  optional: true,
223
227
  });
224
- log.warn('Dataverse Python SDK not found (pip install PowerPlatform-Dataverse-Client pandas)');
228
+ log.info('Dataverse Python SDK not found (optional dv-connect installs it later when you start Dataverse work)');
225
229
  }
226
230
 
227
231
  // Dataverse-skills plugin — HARD GATE. All Dataverse work in this template