@pacaf/wizard-ux 3.5.0 → 3.5.1

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.5.0",
3
+ "version": "3.5.1",
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.4.3"
41
+ "@pacaf/wizard": "3.4.4"
42
42
  },
43
43
  "devDependencies": {
44
44
  "@types/react": "^19.0.0",
@@ -1,5 +1,7 @@
1
1
  // Step 8 - Connectors. Browser-native connector selection and optional data-source binding.
2
- import { existsSync } from 'node:fs';
2
+ // Dataverse is NOT in this list and is never an opt-in toggle: every Code App is bound to
3
+ // Dataverse at the environment level via the mandatory Dataverse URL captured in Step 2.
4
+ import { existsSync, readFileSync } from 'node:fs';
3
5
  import { execFileSync } from 'node:child_process';
4
6
  import { dirname, join, resolve } from 'node:path';
5
7
  import { fileURLToPath, pathToFileURL } from 'node:url';
@@ -19,8 +21,9 @@ const CREATE_MANUAL = '__manual__';
19
21
  const SKIP_CONNECTION = '__skip__';
20
22
  const BAP_PERMISSION_RE = /does not have permission to access|checkAccess|HTTP error status: 403/i;
21
23
 
24
+ // Dataverse is intentionally excluded: it is mandatory and bound at the environment level,
25
+ // not chosen here. See bindDataverse() for the always-on handling.
22
26
  const COMMON_CONNECTORS = [
23
- { apiId: 'shared_commondataserviceforapps', name: 'Dataverse' },
24
27
  { apiId: 'shared_office365users', name: 'Office 365 Users' },
25
28
  { apiId: 'shared_sharepointonline', name: 'SharePoint' },
26
29
  { apiId: 'shared_office365', name: 'Office 365 Outlook' },
@@ -156,6 +159,46 @@ function runFileCapture(log, file, args, opts = {}) {
156
159
  });
157
160
  }
158
161
 
162
+ // Read the planned Dataverse tables produced by the planning workflow
163
+ // (dataverse/register-datasources.plan.json). Missing/invalid plan = no tables yet.
164
+ function dataverseRegistrationPlan(projectDir) {
165
+ const planPath = join(projectDir, 'dataverse', 'register-datasources.plan.json');
166
+ if (!existsSync(planPath)) return { planPath, tables: [] };
167
+ try {
168
+ const plan = JSON.parse(readFileSync(planPath, 'utf-8'));
169
+ const tables = Array.isArray(plan.dataverseTables) ? plan.dataverseTables.filter(Boolean) : [];
170
+ return { planPath, tables };
171
+ } catch {
172
+ return { planPath, tables: [] };
173
+ }
174
+ }
175
+
176
+ // Dataverse is always bound to a Code App — never optional. When planned tables exist and we
177
+ // have a verified user PAC profile, register them now; otherwise emit clear, non-optional status.
178
+ async function bindDataverse(log, { pac, projectDir, canRegister }) {
179
+ log.info('Dataverse is always bound to this Code App at the environment level (the Dataverse URL you confirmed in Step 2). It is never optional.');
180
+ const { tables } = dataverseRegistrationPlan(projectDir);
181
+ if (tables.length === 0) {
182
+ log.info('No Dataverse tables are planned yet. Once your planning payload defines tables, register them with: npm run register:dataverse');
183
+ return [];
184
+ }
185
+ if (!canRegister || !pac) {
186
+ log.info(`${tables.length} planned Dataverse table(s) found. Register them with: npm run register:dataverse (or pac code add-data-source -a dataverse -t <table>).`);
187
+ return [];
188
+ }
189
+ const registered = [];
190
+ for (const table of tables) {
191
+ const result = await runFileCapture(log, pac, ['code', 'add-data-source', '-a', 'dataverse', '-t', table], { cwd: projectDir });
192
+ if (!result.ok || BAP_PERMISSION_RE.test(result.stderr)) {
193
+ log.warn(`Dataverse table ${table} - registration failed. Retry later with: pac code add-data-source -a dataverse -t ${table}`);
194
+ } else {
195
+ log.ok(`Dataverse table ${table} - data source registered`);
196
+ registered.push(table);
197
+ }
198
+ }
199
+ return registered;
200
+ }
201
+
159
202
  function parseCustomConnectors(rawEntries) {
160
203
  const connectors = [];
161
204
  const seen = new Set();
@@ -204,7 +247,7 @@ export default {
204
247
  meta: {
205
248
  number: 8,
206
249
  title: 'Bind Connectors',
207
- description: 'Choose connector references and optionally register connector data sources for the Code App.',
250
+ description: 'Dataverse is always bound at the environment level (Step 2). Here you optionally choose additional connector references and register their data sources.',
208
251
  canRunInBrowser: true,
209
252
  optional: true,
210
253
  needsSecret: true,
@@ -266,8 +309,6 @@ export default {
266
309
  showIf: { id: 'DEFER_CONNECTORS', equals: false },
267
310
  });
268
311
 
269
- if (connector.apiId === 'shared_commondataserviceforapps') continue;
270
-
271
312
  const discovered = discoverConnections(pac, connector.apiId);
272
313
  const savedConnectionId = state.CONNECTOR_CONNECTION_IDS?.[connector.apiId] || '';
273
314
  const defaultValue = savedConnectionId || (discovered.length === 1 ? discovered[0].connectionId : SKIP_CONNECTION);
@@ -324,6 +365,12 @@ export default {
324
365
  if (answers.DEFER_CONNECTORS !== false) {
325
366
  log.ok('Connector binding deferred until prototype validation is complete');
326
367
  if (customRaw.length > 0) log.info(`Recorded connector notes: ${customRaw.join(', ')}`);
368
+ // Dataverse is never deferred — it is bound at the environment level regardless.
369
+ await bindDataverse(log, {
370
+ pac: null,
371
+ projectDir: resolve(String(state.PROJECT_DIR || PROJECT_DIR)),
372
+ canRegister: false,
373
+ });
327
374
  return {
328
375
  stateUpdate: {
329
376
  CONNECTOR_BINDING_DEFERRED: true,
@@ -347,59 +394,55 @@ export default {
347
394
  ...customConnectors.map((connector) => connector.apiId),
348
395
  ])).filter((apiId) => connectorMap.has(apiId));
349
396
 
350
- if (selectedApiIds.length === 0) {
351
- log.warn('No connectors selected. Nothing to bind.');
352
- return {
353
- stateUpdate: {
354
- CONNECTOR_BINDING_DEFERRED: false,
355
- CONNECTOR_API_IDS: [],
356
- CUSTOM_CONNECTORS: customRaw,
357
- },
358
- completedStep: 8,
359
- };
360
- }
361
-
397
+ const projectDir = resolve(String(state.PROJECT_DIR || PROJECT_DIR));
362
398
  const prefix = state.PUBLISHER_PREFIX || '';
363
399
  const solutionName = state.SOLUTION_UNIQUE_NAME || '';
364
- if (!prefix) throw new Error('Publisher prefix is missing. Complete Step 5 before binding connectors.');
365
- if (!solutionName) throw new Error('Solution unique name is missing. Complete Step 6 before binding connectors.');
366
-
367
- log.info('Checking existing connection references...');
368
- let existingRefs = [];
369
- if (!isUserAuth) {
370
- try {
371
- existingRefs = await listConnectionReferences(prefix);
372
- } catch (err) {
373
- log.warn(`Could not query connection references: ${err.message}`);
374
- }
375
- }
376
400
 
377
- if (isUserAuth) {
378
- log.warn('User auth does not support automated connection reference creation via the Dataverse API.');
379
- log.info('Create connection references manually in the Maker Portal:');
380
- log.info(` 1. Go to make.powerapps.com → your Dev environment → Solutions → ${solutionName}`);
381
- log.info(' 2. Add existing → Connection reference → for each connector');
382
- log.info(' 3. Or create them during pac code add-data-source.');
401
+ if (selectedApiIds.length === 0) {
402
+ log.info('No additional connectors selected. Dataverse is still bound automatically.');
383
403
  } else {
384
- log.info('Creating missing connection references in the selected solution...');
385
- for (const apiId of selectedApiIds) {
386
- const connector = connectorMap.get(apiId);
404
+ if (!prefix) throw new Error('Publisher prefix is missing. Complete Step 5 before binding connectors.');
405
+ if (!solutionName) throw new Error('Solution unique name is missing. Complete Step 6 before binding connectors.');
406
+
407
+ log.info('Checking existing connection references...');
408
+ let existingRefs = [];
409
+ if (!isUserAuth) {
387
410
  try {
388
- const created = await createConnectionReference(log, connector, prefix, solutionName, existingRefs);
389
- existingRefs.push(created);
411
+ existingRefs = await listConnectionReferences(prefix);
390
412
  } catch (err) {
391
- if (/already exists|database constraint/i.test(err.message)) log.ok(`${connector.name} - connection reference already exists`);
392
- else log.warn(`${connector.name} - connection reference failed: ${err.message}`);
413
+ log.warn(`Could not query connection references: ${err.message}`);
414
+ }
415
+ }
416
+
417
+ if (isUserAuth) {
418
+ log.warn('User auth does not support automated connection reference creation via the Dataverse API.');
419
+ log.info('Create connection references manually in the Maker Portal:');
420
+ log.info(` 1. Go to make.powerapps.com → your Dev environment → Solutions → ${solutionName}`);
421
+ log.info(' 2. Add existing → Connection reference → for each connector');
422
+ log.info(' 3. Or create them during pac code add-data-source.');
423
+ } else {
424
+ log.info('Creating missing connection references in the selected solution...');
425
+ for (const apiId of selectedApiIds) {
426
+ const connector = connectorMap.get(apiId);
427
+ try {
428
+ const created = await createConnectionReference(log, connector, prefix, solutionName, existingRefs);
429
+ existingRefs.push(created);
430
+ } catch (err) {
431
+ if (/already exists|database constraint/i.test(err.message)) log.ok(`${connector.name} - connection reference already exists`);
432
+ else log.warn(`${connector.name} - connection reference failed: ${err.message}`);
433
+ }
393
434
  }
394
435
  }
395
436
  }
396
437
 
397
438
  const connectionIds = {};
439
+ let dataversePac = null;
440
+ let dataverseCanRegister = false;
441
+
398
442
  if (answers.REGISTER_DATA_SOURCES === true) {
399
443
  const pac = SHELL.pacPath();
400
444
  if (!pac) throw new Error('PAC CLI was not found. Install PAC CLI before registering data sources.');
401
445
 
402
- const projectDir = resolve(String(state.PROJECT_DIR || PROJECT_DIR));
403
446
  if (!existsSync(join(projectDir, 'power.config.json'))) {
404
447
  throw new Error(`power.config.json was not found in ${projectDir}. Complete Step 7 before registering data sources.`);
405
448
  }
@@ -407,8 +450,10 @@ export default {
407
450
  const credentialValues = isUserAuth ? null : resolveCredentialValues(state);
408
451
  const verification = verifyUserProfile(pac, projectDir, state, credentialValues);
409
452
  log.ok(`Verified user profile ${verification.profileName}`);
453
+ dataversePac = pac;
454
+ dataverseCanRegister = true;
410
455
 
411
- for (const apiId of selectedApiIds.filter((id) => id !== 'shared_commondataserviceforapps')) {
456
+ for (const apiId of selectedApiIds) {
412
457
  const connector = connectorMap.get(apiId);
413
458
  const connectionId = connectionIdFromAnswers(answers, connector);
414
459
  if (!connectionId) {
@@ -425,9 +470,12 @@ export default {
425
470
  }
426
471
  }
427
472
  } else {
428
- log.info('Data-source registration skipped. Connection references were handled in the solution.');
473
+ log.info('Connector data-source registration skipped. Connection references were handled in the solution.');
429
474
  }
430
475
 
476
+ // Dataverse is ALWAYS present — registered here when planned tables and a verified profile exist.
477
+ await bindDataverse(log, { pac: dataversePac, projectDir, canRegister: dataverseCanRegister });
478
+
431
479
  return {
432
480
  stateUpdate: {
433
481
  CONNECTOR_BINDING_DEFERRED: false,