@pacaf/wizard-ux 3.3.6 → 3.3.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pacaf/wizard-ux",
3
- "version": "3.3.6",
3
+ "version": "3.3.8",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "description": "Browser-based setup wizard for Power Apps Code Apps (parallel to @pacaf/wizard CLI).",
@@ -38,7 +38,7 @@
38
38
  "react-dom": "^19.0.0",
39
39
  "react-resizable-panels": "^2.1.7",
40
40
  "react-router-dom": "^7.1.0",
41
- "@pacaf/wizard": "3.3.7"
41
+ "@pacaf/wizard": "3.3.9"
42
42
  },
43
43
  "devDependencies": {
44
44
  "@types/react": "^19.0.0",
@@ -11,6 +11,7 @@ const PACKAGE_DIR = resolve(__dirname, '..', '..', '..');
11
11
  const PROJECT_DIR = process.cwd();
12
12
  const SHELL = await import(pathToFileURL(resolve(PACKAGE_DIR, 'wizard', 'lib', 'shell.mjs')).href);
13
13
  const PAC_TARGET = await import(pathToFileURL(resolve(PACKAGE_DIR, 'wizard', 'lib', 'pac-target.mjs')).href);
14
+ const SOLUTION_MEMBERSHIP = await import(pathToFileURL(resolve(PACKAGE_DIR, 'wizard', 'lib', 'solution-membership.mjs')).href);
14
15
 
15
16
  function hasCommand(name) {
16
17
  try {
@@ -114,18 +115,34 @@ function verifyUserProfile(pac, projectDir, state, credentialValues) {
114
115
  // play-URL appId where the canvasapp record GUID is expected and always failed
115
116
  // with "...because it does not exist" (issue #81). So we verify and guide
116
117
  // recovery instead of pretending to repair.
117
- async function verifyAppInSolution(log, pac, projectDir, solutionUniqueName) {
118
- if (!solutionUniqueName) return;
119
- log.info(`Verifying app is a component of solution "${solutionUniqueName}"...`);
120
- const { ok, stdout, stderr } = await runFileCapture(log, pac, ['solution', 'list'], { cwd: projectDir });
121
- const combined = `${stdout}\n${stderr}`;
122
- if (ok && new RegExp(`\\b${solutionUniqueName}\\b`, 'i').test(combined)) {
123
- log.ok(`Solution "${solutionUniqueName}" exists and the push carried -s — the app should now appear under it.`);
124
- log.info(`Confirm in Maker Portal Solutions ${solutionUniqueName} Apps.`);
125
- } else {
126
- log.warn(`Could not confirm solution "${solutionUniqueName}". Verify in the Maker Portal that the app is listed under it.`);
118
+ //
119
+ // This is an AUTHORITATIVE check: it exports the solution and counts Canvas App
120
+ // (type 300) components. The old `pac solution list` check was a FALSE POSITIVE
121
+ // it only proved the solution exists, not that the app is inside it, which is
122
+ // exactly how an app could be left orphaned while the wizard reported success.
123
+ async function verifyAppInSolution(log, pac, projectDir, solutionUniqueName, { phase, appDisplayName } = {}) {
124
+ if (!solutionUniqueName) return { status: 'unknown' };
125
+ log.info(`Verifying app is a component of solution "${solutionUniqueName}" (exporting solution to inspect components)...`);
126
+ const runCapture = (file, args, opts) => runFileCapture(log, file, args, opts);
127
+ const membership = await SOLUTION_MEMBERSHIP.checkAppInSolution({ pac, projectDir, solutionUniqueName, runCapture });
128
+ if (membership.status === 'member') {
129
+ log.ok(`Confirmed: the app is a component of solution "${solutionUniqueName}" (${membership.canvasComponentCount} Canvas App component(s)).`);
130
+ return membership;
131
+ }
132
+ if (membership.status === 'absent') {
133
+ // INFORMATIONAL ONLY — never block the deploy on this signal. The documented
134
+ // contract (learn.microsoft.com/power-apps/developer/code-apps/how-to/alm)
135
+ // is simply `pac code push --solutionName <name>`; association happens as
136
+ // part of that push. The export+type-300 component count below is a
137
+ // best-effort cross-check, NOT an authoritative source of truth — a clean
138
+ // export with zero Canvas App components does NOT reliably mean the app is
139
+ // orphaned. So we surface a hint and continue rather than throwing.
140
+ const hint = SOLUTION_MEMBERSHIP.orphanRecoverySteps(solutionUniqueName, appDisplayName || 'this Code App')[0];
141
+ log.warn(`${hint} If it is missing in the Maker Portal, add it with: Solutions → ${solutionUniqueName} → Add existing → App → Code app.`);
142
+ return membership;
127
143
  }
128
- log.info('If the app is NOT listed under the solution, it was pushed without a valid solution unique name. A later -s re-push will NOT fix it — delete the orphaned Code App in the Maker Portal, then re-push with the UNIQUE name.');
144
+ log.warn(`Could not confirm solution membership for "${solutionUniqueName}" (${membership.detail}). Verify in the Maker Portal that the app is listed under it.`);
145
+ return membership;
129
146
  }
130
147
 
131
148
  export default {
@@ -193,12 +210,27 @@ export default {
193
210
  // the solution — association only happens on the first push WITH the unique
194
211
  // name, and a later -s re-push cannot fix it (issue #81).
195
212
  const solutionUniqueName = String(state.SOLUTION_UNIQUE_NAME || '').trim();
213
+ const appDisplayName = String(state.APP_NAME || 'this Code App').trim();
196
214
  const pushArgs = ['code', 'push'];
197
215
  if (solutionUniqueName) {
198
216
  pushArgs.push('-s', solutionUniqueName);
199
217
  } else {
200
218
  throw new Error('No solution unique name (SOLUTION_UNIQUE_NAME) is available. Refusing to run a bare `pac code push`, which would create the Code App outside any solution (a silent failure a later -s re-push cannot fix). Re-run the wizard solution step so the unique name is captured before deploying.');
201
219
  }
220
+
221
+ // PRE-PUSH ORPHAN GUARD. Solution membership is decided ONLY on the CREATE
222
+ // (first push, when power.config.json has no appId). If an appId already
223
+ // exists this push is an UPDATE and -s is ignored — so if the app is not
224
+ // already a component of the selected solution, NO push can ever fix it.
225
+ // Catch that here and hard-stop with recovery instructions instead of
226
+ // wasting an update that silently leaves the app orphaned.
227
+ const preInfo = PAC_TARGET.loadPowerConfigInfo(powerConfigPath);
228
+ const isFirstPush = !preInfo.appId;
229
+ if (!isFirstPush && solutionUniqueName) {
230
+ log.info(`Existing appId detected (${preInfo.appId}) — this push is an UPDATE. Confirming the app is already in solution "${solutionUniqueName}" before pushing...`);
231
+ await verifyAppInSolution(log, pac, projectDir, solutionUniqueName, { phase: 'pre-push', appDisplayName });
232
+ }
233
+
202
234
  const pushResult = await runFileCapture(log, pac, pushArgs, { cwd: projectDir });
203
235
  const pushOutput = `${pushResult.stdout}\n${pushResult.stderr}`;
204
236
  if (!pushResult.ok || PAC_HTTP_ERROR_RE.test(pushOutput)) throw new Error('pac code push failed. Check the live output above, then retry.');
@@ -220,7 +252,13 @@ export default {
220
252
  log.warn('Could not detect deployed app URL in pac output. Open the app from Power Apps Maker Portal.');
221
253
  }
222
254
 
223
- await verifyAppInSolution(log, pac, projectDir, solutionUniqueName);
255
+ // POST-CREATE VERIFICATION. On a first push (CREATE) the -s flag is what
256
+ // associates the app. Authoritatively confirm it actually landed in the
257
+ // solution; if not, the create silently failed to associate and we hard-stop
258
+ // rather than report a false success (the bug that orphaned AI-PMOv3).
259
+ if (isFirstPush) {
260
+ await verifyAppInSolution(log, pac, projectDir, solutionUniqueName, { phase: 'post-create', appDisplayName });
261
+ }
224
262
 
225
263
  return { stateUpdate, completedStep: 9 };
226
264
  },