@pacaf/wizard-ux 3.5.2 → 3.6.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/dist/assets/{index-CJCvBXAD.js → index-CKpAviup.js} +37 -37
- package/dist/index.html +1 -1
- package/package.json +2 -2
- package/server/lib/pac-parse.mjs +55 -0
- package/server/steps/01-prerequisites.mjs +133 -2
- package/server/steps/02-project-and-env.mjs +10 -73
- package/server/steps/04-auth-setup.mjs +42 -286
- package/server/steps/05-environments.mjs +436 -0
- package/server/steps/{05-publisher.mjs → 06-publisher.mjs} +9 -57
- package/server/steps/{06-solution.mjs → 07-solution.mjs} +8 -8
- package/server/steps/{07-scaffold.mjs → 08-scaffold.mjs} +8 -8
- package/server/steps/{08-connectors.mjs → 09-connectors.mjs} +13 -13
- package/server/steps/{09-verify-deploy.mjs → 10-verify-deploy.mjs} +6 -6
- package/server/steps/{10-add-to-solution.mjs → 11-add-to-solution.mjs} +2 -2
- package/server/steps/index.mjs +8 -7
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-
|
|
31
|
+
<script type="module" crossorigin src="/assets/index-CKpAviup.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.
|
|
3
|
+
"version": "3.6.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.
|
|
41
|
+
"@pacaf/wizard": "3.4.5"
|
|
42
42
|
},
|
|
43
43
|
"devDependencies": {
|
|
44
44
|
"@types/react": "^19.0.0",
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
// Shared PAC CLI output parsing helpers.
|
|
2
|
+
//
|
|
3
|
+
// The PAC CLI emits column-aligned tabular text (no JSON option for several
|
|
4
|
+
// commands such as `pac env list` and `pac solution list`). This parser finds
|
|
5
|
+
// the header line, derives column boundaries from header token positions, and
|
|
6
|
+
// slices each subsequent data line by those boundaries. It handles multi-word
|
|
7
|
+
// values (e.g. "Climb Tracker") and aliased columns (e.g. "pub.customizationprefix").
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Parse PAC CLI column-aligned tabular output into an array of row objects.
|
|
11
|
+
* @param {string} output The raw stdout from a PAC command.
|
|
12
|
+
* @param {string[]} [headerHints] Lowercase substrings used to locate the header row.
|
|
13
|
+
* @returns {Array<Record<string, string>>}
|
|
14
|
+
*/
|
|
15
|
+
export function parsePacTabularRows(output, headerHints) {
|
|
16
|
+
const allLines = String(output || '').split(/\r?\n/);
|
|
17
|
+
const hints = headerHints || ['uniquename', 'solutionid', 'friendlyname'];
|
|
18
|
+
|
|
19
|
+
// Find the header line — first line containing at least one of the hints.
|
|
20
|
+
let headerIdx = -1;
|
|
21
|
+
for (let i = 0; i < allLines.length; i++) {
|
|
22
|
+
const lower = allLines[i].toLowerCase();
|
|
23
|
+
if (hints.some((h) => lower.includes(h))) {
|
|
24
|
+
headerIdx = i;
|
|
25
|
+
break;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
if (headerIdx < 0) return [];
|
|
29
|
+
|
|
30
|
+
const headerLine = allLines[headerIdx];
|
|
31
|
+
|
|
32
|
+
// Extract column names and their start positions from the header.
|
|
33
|
+
const cols = [];
|
|
34
|
+
const re = /\S+/g;
|
|
35
|
+
let m;
|
|
36
|
+
while ((m = re.exec(headerLine)) !== null) {
|
|
37
|
+
cols.push({ name: m[0].toLowerCase(), start: m.index });
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// Parse each data line after the header.
|
|
41
|
+
const rows = [];
|
|
42
|
+
for (let i = headerIdx + 1; i < allLines.length; i++) {
|
|
43
|
+
const line = allLines[i];
|
|
44
|
+
if (!line.trim()) continue;
|
|
45
|
+
if (/^(Connected|Microsoft|Version:|Online|Feedback)/i.test(line.trim())) continue;
|
|
46
|
+
const row = {};
|
|
47
|
+
for (let c = 0; c < cols.length; c++) {
|
|
48
|
+
const start = cols[c].start;
|
|
49
|
+
const end = c < cols.length - 1 ? cols[c + 1].start : line.length;
|
|
50
|
+
row[cols[c].name] = (start < line.length ? line.slice(start, end) : '').trim();
|
|
51
|
+
}
|
|
52
|
+
if (Object.values(row).some((v) => v)) rows.push(row);
|
|
53
|
+
}
|
|
54
|
+
return rows;
|
|
55
|
+
}
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
// Step 1 — Prerequisites. Read-only checks, no questions.
|
|
2
|
-
import { platform } from 'node:os';
|
|
2
|
+
import { platform, homedir } from 'node:os';
|
|
3
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
4
|
+
import { join } from 'node:path';
|
|
3
5
|
import { execFileSync, execSync } from 'node:child_process';
|
|
4
6
|
import { pacPath, runSafe } from '@pacaf/wizard/lib/shell.mjs';
|
|
5
7
|
|
|
@@ -16,11 +18,75 @@ function tryRun(cmd) {
|
|
|
16
18
|
catch { return null; }
|
|
17
19
|
}
|
|
18
20
|
|
|
21
|
+
/**
|
|
22
|
+
* Best-effort coding-agent detection (env-only, read-only).
|
|
23
|
+
* Used to surface the correct Dataverse-skills plugin install command.
|
|
24
|
+
* Defaults to GitHub Copilot CLI when the agent can't be determined.
|
|
25
|
+
*/
|
|
26
|
+
function detectAgentInstall() {
|
|
27
|
+
const env = process.env;
|
|
28
|
+
if (env.CLAUDE) {
|
|
29
|
+
return {
|
|
30
|
+
id: 'claude',
|
|
31
|
+
label: 'Claude Code',
|
|
32
|
+
install: 'claude plugin install dataverse@claude-plugins-official',
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
// Default — GitHub Copilot CLI is the recommended path for the plugin.
|
|
36
|
+
return {
|
|
37
|
+
id: 'copilot',
|
|
38
|
+
label: 'GitHub Copilot CLI',
|
|
39
|
+
install: '/plugin install dataverse@awesome-copilot',
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Detect whether the Dataverse-skills plugin is installed (filesystem + config),
|
|
45
|
+
* without attempting to install it. The install is interactive/manual.
|
|
46
|
+
*/
|
|
47
|
+
function detectDataversePlugin() {
|
|
48
|
+
const home = homedir();
|
|
49
|
+
|
|
50
|
+
// GitHub Copilot — git-clone cache path
|
|
51
|
+
const copilotSkills = join(home, '.copilot', 'installed-plugins', 'awesome-copilot', 'dataverse', 'skills');
|
|
52
|
+
if (existsSync(copilotSkills)) return true;
|
|
53
|
+
|
|
54
|
+
// GitHub Copilot — config.json installedPlugins[] entry
|
|
55
|
+
try {
|
|
56
|
+
const cfgPath = join(home, '.copilot', 'config.json');
|
|
57
|
+
if (existsSync(cfgPath)) {
|
|
58
|
+
const cfg = JSON.parse(readFileSync(cfgPath, 'utf-8'));
|
|
59
|
+
const entry = (cfg.installedPlugins || []).find((p) => p && p.name === 'dataverse');
|
|
60
|
+
if (entry && entry.enabled !== false && entry.cache_path && existsSync(entry.cache_path)) {
|
|
61
|
+
return true;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
} catch { /* ignore malformed config */ }
|
|
65
|
+
|
|
66
|
+
// Claude Code — installed-plugins cache (best-effort path candidates)
|
|
67
|
+
const claudeCandidates = [
|
|
68
|
+
join(home, '.claude', 'installed-plugins', 'claude-plugins-official', 'dataverse'),
|
|
69
|
+
join(home, '.claude', 'plugins', 'dataverse'),
|
|
70
|
+
];
|
|
71
|
+
if (claudeCandidates.some((p) => existsSync(p))) return true;
|
|
72
|
+
|
|
73
|
+
return false;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/** Verify the Dataverse Python SDK (PowerPlatform-Dataverse-Client + pandas) imports. */
|
|
77
|
+
function detectDataverseSdk(pythonCmd) {
|
|
78
|
+
if (!pythonCmd) return false;
|
|
79
|
+
const exec = pythonCmd === 'py' ? 'py -3' : pythonCmd;
|
|
80
|
+
const out = tryRun(`${exec} -c "import pandas, PowerPlatform_Dataverse_Client"`);
|
|
81
|
+
// tryRun returns '' on success (no stdout), null on non-zero exit.
|
|
82
|
+
return out !== null;
|
|
83
|
+
}
|
|
84
|
+
|
|
19
85
|
export default {
|
|
20
86
|
meta: {
|
|
21
87
|
number: 1,
|
|
22
88
|
title: 'Prerequisites',
|
|
23
|
-
description: 'Verify Node, Git, .NET SDK, PAC CLI, Python 3, and optional tools are present.',
|
|
89
|
+
description: 'Verify Node, Git, .NET SDK, PAC CLI, Python 3, the Dataverse-skills plugin, and optional tools are present.',
|
|
24
90
|
canRunInBrowser: true,
|
|
25
91
|
readOnly: true,
|
|
26
92
|
},
|
|
@@ -141,6 +207,71 @@ export default {
|
|
|
141
207
|
// Note: The Dataverse-skills plugin manages its own Python SDK installation
|
|
142
208
|
// via the dv-connect skill. No separate pip install needed here.
|
|
143
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.
|
|
212
|
+
const sdkOk = detectDataverseSdk(pythonCmd);
|
|
213
|
+
if (sdkOk) {
|
|
214
|
+
checks.push({ name: 'Dataverse Python SDK', ok: true, value: 'available', hint: null });
|
|
215
|
+
log.ok('Dataverse Python SDK (PowerPlatform-Dataverse-Client + pandas)');
|
|
216
|
+
} else {
|
|
217
|
+
checks.push({
|
|
218
|
+
name: 'Dataverse Python SDK',
|
|
219
|
+
ok: false,
|
|
220
|
+
value: null,
|
|
221
|
+
hint: 'Run: pip install PowerPlatform-Dataverse-Client pandas',
|
|
222
|
+
optional: true,
|
|
223
|
+
});
|
|
224
|
+
log.warn('Dataverse Python SDK — not found (pip install PowerPlatform-Dataverse-Client pandas)');
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
// Dataverse-skills plugin — HARD GATE. All Dataverse work in this template
|
|
228
|
+
// (schema, data, queries, solution lifecycle, env admin, security) is delegated
|
|
229
|
+
// to this plugin. It is the first-class, only-supported path.
|
|
230
|
+
const pluginOk = detectDataversePlugin();
|
|
231
|
+
if (pluginOk) {
|
|
232
|
+
checks.push({ name: 'Dataverse-skills plugin', ok: true, value: 'installed', hint: null });
|
|
233
|
+
log.ok('Dataverse-skills plugin installed');
|
|
234
|
+
log.info(' → Reminder: MCP tools only load after restarting your editor / CLI');
|
|
235
|
+
} else {
|
|
236
|
+
const agent = detectAgentInstall();
|
|
237
|
+
checks.push({
|
|
238
|
+
name: 'Dataverse-skills plugin',
|
|
239
|
+
ok: false,
|
|
240
|
+
value: null,
|
|
241
|
+
hint: `Required (${agent.label}). Install: ${agent.install} — then run: pip install PowerPlatform-Dataverse-Client pandas, and restart your editor.`,
|
|
242
|
+
});
|
|
243
|
+
log.fail('Dataverse-skills plugin — not installed');
|
|
244
|
+
// Plain-language hard-block guidance. The FIRST thing the user reads is the
|
|
245
|
+
// Copilot CLI vs VS Code Copilot chat distinction, which trips up most people.
|
|
246
|
+
log.info('');
|
|
247
|
+
log.info(' ── Install the Dataverse-skills plugin ─────────────────────────');
|
|
248
|
+
log.info(' IMPORTANT: these steps use the GitHub Copilot CLI (a terminal app),');
|
|
249
|
+
log.info(' NOT the Copilot chat inside VS Code. They are different tools. The');
|
|
250
|
+
log.info(' CLI is what installs the Dataverse plugin reliably.');
|
|
251
|
+
log.info('');
|
|
252
|
+
if (agent.id === 'claude') {
|
|
253
|
+
log.info(' ▶ Claude / Claude Code (detected):');
|
|
254
|
+
log.info(' 1. Open your terminal.');
|
|
255
|
+
log.info(' 2. claude plugin marketplace add <claude-plugins-official repo>');
|
|
256
|
+
log.info(' 3. claude plugin install dataverse@claude-plugins-official');
|
|
257
|
+
log.info(' 4. pip install PowerPlatform-Dataverse-Client pandas');
|
|
258
|
+
log.info(' 5. Close and reopen Claude Code, then click "Run checks".');
|
|
259
|
+
} else {
|
|
260
|
+
log.info(' ▶ GitHub Copilot CLI (default):');
|
|
261
|
+
log.info(' 1. Open your terminal.');
|
|
262
|
+
log.info(' 2. (If you don\'t have it) install the CLI: npm install -g @github/copilot');
|
|
263
|
+
log.info(' 3. Start it: type copilot and press Enter. First time: follow the sign-in prompt.');
|
|
264
|
+
log.info(' 4. At the Copilot prompt, type: /plugin install dataverse@awesome-copilot');
|
|
265
|
+
log.info(' 5. Wait for "Installed", then type /exit');
|
|
266
|
+
log.info(' 6. Run: pip install PowerPlatform-Dataverse-Client pandas');
|
|
267
|
+
log.info(' 7. Restart your editor so the MCP tools load, then click "Run checks".');
|
|
268
|
+
}
|
|
269
|
+
log.info('');
|
|
270
|
+
log.info(' Full guide: docs/dataverse-skills-setup.md');
|
|
271
|
+
log.info(' ────────────────────────────────────────────────────────────────');
|
|
272
|
+
allOk = false;
|
|
273
|
+
}
|
|
274
|
+
|
|
144
275
|
return {
|
|
145
276
|
stateUpdate: { HAS_OP: hasOp, PYTHON_CMD: pythonCmd },
|
|
146
277
|
result: { allOk, checks },
|
|
@@ -1,18 +1,14 @@
|
|
|
1
|
-
// Step 2 — Project name
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
const VALIDATE_PATH = resolve(__dirname, '..', '..', '..', 'wizard', 'lib', 'validate.mjs');
|
|
7
|
-
const { isValidDataverseUrl, normalizeDataverseUrl } = await import(pathToFileURL(VALIDATE_PATH).href);
|
|
8
|
-
|
|
9
|
-
const URL_HINT = 'Format: https://org-name.crm.dynamics.com (the wizard adds https:// for you if you paste it without).';
|
|
1
|
+
// Step 2 — Project name.
|
|
2
|
+
//
|
|
3
|
+
// Environment URLs are NO LONGER captured here. After authentication (Step 4),
|
|
4
|
+
// the "Environments" step (Step 5) discovers environments via `pac env list`
|
|
5
|
+
// and lets the user pick Dev/Test/Prod from the results — no URL pasting.
|
|
10
6
|
|
|
11
7
|
export default {
|
|
12
8
|
meta: {
|
|
13
9
|
number: 2,
|
|
14
|
-
title: 'Project
|
|
15
|
-
description: 'Name your app
|
|
10
|
+
title: 'Project',
|
|
11
|
+
description: 'Name your app. You will pick your Power Platform environments after signing in — no URLs to paste.',
|
|
16
12
|
canRunInBrowser: true,
|
|
17
13
|
},
|
|
18
14
|
|
|
@@ -26,80 +22,21 @@ export default {
|
|
|
26
22
|
required: true,
|
|
27
23
|
defaultValue: state.APP_NAME || '',
|
|
28
24
|
},
|
|
29
|
-
{
|
|
30
|
-
id: 'PP_ENV_DEV',
|
|
31
|
-
type: 'url',
|
|
32
|
-
label: 'Dev environment URL',
|
|
33
|
-
help: 'The URL of your Power Platform development environment. Paste it straight from PPAC — with or without https://. ' + URL_HINT,
|
|
34
|
-
why: [
|
|
35
|
-
"If you haven't created one yet:",
|
|
36
|
-
'1. Open https://admin.powerplatform.microsoft.com',
|
|
37
|
-
'2. Environments → + New',
|
|
38
|
-
'3. Type: Developer or Sandbox · toggle "Add Dataverse" YES',
|
|
39
|
-
'4. Save, wait for provisioning, then copy the Environment URL.',
|
|
40
|
-
].join('\n'),
|
|
41
|
-
required: true,
|
|
42
|
-
defaultValue: state.PP_ENV_DEV || '',
|
|
43
|
-
validatePattern: 'dataverseUrl',
|
|
44
|
-
},
|
|
45
|
-
{
|
|
46
|
-
id: 'PP_ENV_TEST',
|
|
47
|
-
type: 'url',
|
|
48
|
-
label: 'Test environment URL',
|
|
49
|
-
help: 'Optional. Leave blank if you do not have a separate Test environment yet.',
|
|
50
|
-
required: false,
|
|
51
|
-
defaultValue: state.PP_ENV_TEST || '',
|
|
52
|
-
validatePattern: 'dataverseUrl',
|
|
53
|
-
},
|
|
54
|
-
{
|
|
55
|
-
id: 'PP_ENV_PROD',
|
|
56
|
-
type: 'url',
|
|
57
|
-
label: 'Prod environment URL',
|
|
58
|
-
help: 'Optional. Leave blank if you do not have a Prod environment yet.',
|
|
59
|
-
required: false,
|
|
60
|
-
defaultValue: state.PP_ENV_PROD || '',
|
|
61
|
-
validatePattern: 'dataverseUrl',
|
|
62
|
-
},
|
|
63
25
|
];
|
|
64
26
|
},
|
|
65
27
|
|
|
66
28
|
async apply(answers, _state, log) {
|
|
67
|
-
const errors = {};
|
|
68
|
-
const norm = (s) => normalizeDataverseUrl(s);
|
|
69
|
-
|
|
70
29
|
const appName = (answers.APP_NAME || '').trim();
|
|
71
|
-
if (!appName)
|
|
72
|
-
|
|
73
|
-
const dev = norm(answers.PP_ENV_DEV);
|
|
74
|
-
if (!dev) errors.PP_ENV_DEV = 'Required';
|
|
75
|
-
else if (!isValidDataverseUrl(dev) && !isValidDataverseUrl(dev + '/')) errors.PP_ENV_DEV = URL_HINT;
|
|
76
|
-
|
|
77
|
-
const test = norm(answers.PP_ENV_TEST);
|
|
78
|
-
if (test && !isValidDataverseUrl(test) && !isValidDataverseUrl(test + '/')) {
|
|
79
|
-
log.warn(`Test URL "${test}" doesn't look standard, saving anyway.`);
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
const prod = norm(answers.PP_ENV_PROD);
|
|
83
|
-
if (prod && !isValidDataverseUrl(prod) && !isValidDataverseUrl(prod + '/')) {
|
|
84
|
-
log.warn(`Prod URL "${prod}" doesn't look standard, saving anyway.`);
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
if (Object.keys(errors).length > 0) {
|
|
88
|
-
const msg = Object.entries(errors).map(([k, v]) => `${k}: ${v}`).join('; ');
|
|
89
|
-
throw new Error(`Validation failed — ${msg}`);
|
|
30
|
+
if (!appName) {
|
|
31
|
+
throw new Error('Validation failed — APP_NAME: Required');
|
|
90
32
|
}
|
|
91
33
|
|
|
92
34
|
log.ok(`App name: ${appName}`);
|
|
93
|
-
log.
|
|
94
|
-
if (test) log.ok(`Test env: ${test}`); else log.info('Test env: (skipped)');
|
|
95
|
-
if (prod) log.ok(`Prod env: ${prod}`); else log.info('Prod env: (skipped)');
|
|
35
|
+
log.info('Environments are selected after sign-in (Step 5) — no URLs needed here.');
|
|
96
36
|
|
|
97
37
|
return {
|
|
98
38
|
stateUpdate: {
|
|
99
39
|
APP_NAME: appName,
|
|
100
|
-
PP_ENV_DEV: dev,
|
|
101
|
-
PP_ENV_TEST: test,
|
|
102
|
-
PP_ENV_PROD: prod,
|
|
103
40
|
WIZARD_TARGET_ENV: 'dev',
|
|
104
41
|
},
|
|
105
42
|
completedStep: 2,
|