@pacaf/wizard-ux 3.3.5 → 3.3.7
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.
|
|
3
|
+
"version": "3.3.7",
|
|
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.
|
|
41
|
+
"@pacaf/wizard": "3.3.8"
|
|
42
42
|
},
|
|
43
43
|
"devDependencies": {
|
|
44
44
|
"@types/react": "^19.0.0",
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// Step 9 - Verify & Deploy. Browser-native build and optional pac code push.
|
|
2
|
-
import { existsSync
|
|
2
|
+
import { existsSync } from 'node:fs';
|
|
3
3
|
import { spawn, execFileSync } from 'node:child_process';
|
|
4
4
|
import { dirname, join, resolve } from 'node:path';
|
|
5
5
|
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
@@ -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 {
|
|
@@ -39,22 +40,6 @@ function runCommand(log, command, opts = {}) {
|
|
|
39
40
|
});
|
|
40
41
|
}
|
|
41
42
|
|
|
42
|
-
function runFile(log, file, args, opts = {}) {
|
|
43
|
-
return new Promise((resolvePromise) => {
|
|
44
|
-
log.info(`$ ${SHELL.formatCommandForLog(file, args)}`);
|
|
45
|
-
const child = SHELL.spawnSafe(file, args, { cwd: opts.cwd || PROJECT_DIR, stdio: ['ignore', 'pipe', 'pipe'] });
|
|
46
|
-
child.stdout.setEncoding('utf-8');
|
|
47
|
-
child.stderr.setEncoding('utf-8');
|
|
48
|
-
child.stdout.on('data', (chunk) => log.info(String(chunk).trimEnd()));
|
|
49
|
-
child.stderr.on('data', (chunk) => log.warn(String(chunk).trimEnd()));
|
|
50
|
-
child.on('error', (error) => {
|
|
51
|
-
log.fail(`Failed to start ${file}: ${error.message}`);
|
|
52
|
-
resolvePromise(false);
|
|
53
|
-
});
|
|
54
|
-
child.on('close', (code) => resolvePromise(code === 0));
|
|
55
|
-
});
|
|
56
|
-
}
|
|
57
|
-
|
|
58
43
|
function runFileCapture(log, file, args, opts = {}) {
|
|
59
44
|
return new Promise((resolvePromise) => {
|
|
60
45
|
log.info(`$ ${SHELL.formatCommandForLog(file, args)}`);
|
|
@@ -120,21 +105,39 @@ function verifyUserProfile(pac, projectDir, state, credentialValues) {
|
|
|
120
105
|
});
|
|
121
106
|
}
|
|
122
107
|
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
108
|
+
// Verify the deployed Code App joined the selected solution.
|
|
109
|
+
//
|
|
110
|
+
// Association happens during `pac code push -s <UNIQUE name>` on the FIRST
|
|
111
|
+
// push — it is what creates the Dataverse `canvasapps` record inside the
|
|
112
|
+
// solution. A later `-s` re-push does NOT retroactively associate an app that
|
|
113
|
+
// was first pushed bare, and there is no reliable post-hoc CLI fix: the old
|
|
114
|
+
// `solution add-solution-component -ct 300` path passed the Code App's
|
|
115
|
+
// play-URL appId where the canvasapp record GUID is expected and always failed
|
|
116
|
+
// with "...because it does not exist" (issue #81). So we verify and guide
|
|
117
|
+
// recovery instead of pretending to repair.
|
|
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;
|
|
130
131
|
}
|
|
131
|
-
if (
|
|
132
|
-
|
|
133
|
-
|
|
132
|
+
if (membership.status === 'absent') {
|
|
133
|
+
const steps = SOLUTION_MEMBERSHIP.orphanRecoverySteps(solutionUniqueName, appDisplayName || 'this Code App');
|
|
134
|
+
for (const line of steps) log.fail(line);
|
|
135
|
+
// A confirmed orphan is a hard error in BOTH phases: pre-push (an UPDATE
|
|
136
|
+
// cannot fix it) and post-create (the create silently failed to associate).
|
|
137
|
+
throw new Error(`Code App is orphaned — NOT a component of solution "${solutionUniqueName}". ${phase === 'pre-push' ? 'A re-push will not fix this.' : 'The create did not associate it.'} See the recovery steps above.`);
|
|
134
138
|
}
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
else log.warn(`Could not add app to solution ${solutionName}. It may already be present, or you can add it manually.`);
|
|
139
|
+
log.warn(`Could not confirm solution membership for "${solutionUniqueName}" (${membership.detail}). Verify in the Maker Portal that the app is listed under it.`);
|
|
140
|
+
return membership;
|
|
138
141
|
}
|
|
139
142
|
|
|
140
143
|
export default {
|
|
@@ -197,8 +200,32 @@ export default {
|
|
|
197
200
|
const verification = verifyUserProfile(pac, projectDir, state, credentialValues);
|
|
198
201
|
log.ok(`Verified user profile ${verification.profileName}`);
|
|
199
202
|
|
|
203
|
+
// -s MUST be the solution UNIQUE name. Passing the friendly display name
|
|
204
|
+
// (e.g. "AI PMO" with a space) silently no-ops and leaves the app OUTSIDE
|
|
205
|
+
// the solution — association only happens on the first push WITH the unique
|
|
206
|
+
// name, and a later -s re-push cannot fix it (issue #81).
|
|
207
|
+
const solutionUniqueName = String(state.SOLUTION_UNIQUE_NAME || '').trim();
|
|
208
|
+
const appDisplayName = String(state.APP_NAME || 'this Code App').trim();
|
|
200
209
|
const pushArgs = ['code', 'push'];
|
|
201
|
-
if (
|
|
210
|
+
if (solutionUniqueName) {
|
|
211
|
+
pushArgs.push('-s', solutionUniqueName);
|
|
212
|
+
} else {
|
|
213
|
+
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.');
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
// PRE-PUSH ORPHAN GUARD. Solution membership is decided ONLY on the CREATE
|
|
217
|
+
// (first push, when power.config.json has no appId). If an appId already
|
|
218
|
+
// exists this push is an UPDATE and -s is ignored — so if the app is not
|
|
219
|
+
// already a component of the selected solution, NO push can ever fix it.
|
|
220
|
+
// Catch that here and hard-stop with recovery instructions instead of
|
|
221
|
+
// wasting an update that silently leaves the app orphaned.
|
|
222
|
+
const preInfo = PAC_TARGET.loadPowerConfigInfo(powerConfigPath);
|
|
223
|
+
const isFirstPush = !preInfo.appId;
|
|
224
|
+
if (!isFirstPush && solutionUniqueName) {
|
|
225
|
+
log.info(`Existing appId detected (${preInfo.appId}) — this push is an UPDATE. Confirming the app is already in solution "${solutionUniqueName}" before pushing...`);
|
|
226
|
+
await verifyAppInSolution(log, pac, projectDir, solutionUniqueName, { phase: 'pre-push', appDisplayName });
|
|
227
|
+
}
|
|
228
|
+
|
|
202
229
|
const pushResult = await runFileCapture(log, pac, pushArgs, { cwd: projectDir });
|
|
203
230
|
const pushOutput = `${pushResult.stdout}\n${pushResult.stderr}`;
|
|
204
231
|
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 +247,13 @@ export default {
|
|
|
220
247
|
log.warn('Could not detect deployed app URL in pac output. Open the app from Power Apps Maker Portal.');
|
|
221
248
|
}
|
|
222
249
|
|
|
223
|
-
|
|
250
|
+
// POST-CREATE VERIFICATION. On a first push (CREATE) the -s flag is what
|
|
251
|
+
// associates the app. Authoritatively confirm it actually landed in the
|
|
252
|
+
// solution; if not, the create silently failed to associate and we hard-stop
|
|
253
|
+
// rather than report a false success (the bug that orphaned AI-PMOv3).
|
|
254
|
+
if (isFirstPush) {
|
|
255
|
+
await verifyAppInSolution(log, pac, projectDir, solutionUniqueName, { phase: 'post-create', appDisplayName });
|
|
256
|
+
}
|
|
224
257
|
|
|
225
258
|
return { stateUpdate, completedStep: 9 };
|
|
226
259
|
},
|