@msn-control/liftoff 0.4.1 → 0.5.0
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/README.md +65 -256
- package/assets/power-apps-code-app/3438c352483e40982f6c5c0fc36fd71f8e7adbbb/UPSTREAM_LICENSE.txt +21 -0
- package/assets/power-apps-code-app/3438c352483e40982f6c5c0fc36fd71f8e7adbbb/catalog.json +520 -0
- package/assets/power-apps-code-app/3438c352483e40982f6c5c0fc36fd71f8e7adbbb/packaged/gitignore +27 -0
- package/assets/power-apps-code-app/3438c352483e40982f6c5c0fc36fd71f8e7adbbb/starter/README.md +25 -0
- package/assets/power-apps-code-app/3438c352483e40982f6c5c0fc36fd71f8e7adbbb/starter/components.json +22 -0
- package/assets/power-apps-code-app/3438c352483e40982f6c5c0fc36fd71f8e7adbbb/starter/eslint.config.js +23 -0
- package/assets/power-apps-code-app/3438c352483e40982f6c5c0fc36fd71f8e7adbbb/starter/index.html +13 -0
- package/assets/power-apps-code-app/3438c352483e40982f6c5c0fc36fd71f8e7adbbb/starter/package-lock.json +6597 -0
- package/assets/power-apps-code-app/3438c352483e40982f6c5c0fc36fd71f8e7adbbb/starter/package.json +60 -0
- package/assets/power-apps-code-app/3438c352483e40982f6c5c0fc36fd71f8e7adbbb/starter/public/power-apps.svg +55 -0
- package/assets/power-apps-code-app/3438c352483e40982f6c5c0fc36fd71f8e7adbbb/starter/src/App.tsx +17 -0
- package/assets/power-apps-code-app/3438c352483e40982f6c5c0fc36fd71f8e7adbbb/starter/src/assets/react.svg +1 -0
- package/assets/power-apps-code-app/3438c352483e40982f6c5c0fc36fd71f8e7adbbb/starter/src/components/mode-toggle.tsx +37 -0
- package/assets/power-apps-code-app/3438c352483e40982f6c5c0fc36fd71f8e7adbbb/starter/src/components/ui/badge.tsx +46 -0
- package/assets/power-apps-code-app/3438c352483e40982f6c5c0fc36fd71f8e7adbbb/starter/src/components/ui/button.tsx +60 -0
- package/assets/power-apps-code-app/3438c352483e40982f6c5c0fc36fd71f8e7adbbb/starter/src/components/ui/calendar.tsx +211 -0
- package/assets/power-apps-code-app/3438c352483e40982f6c5c0fc36fd71f8e7adbbb/starter/src/components/ui/card.tsx +92 -0
- package/assets/power-apps-code-app/3438c352483e40982f6c5c0fc36fd71f8e7adbbb/starter/src/components/ui/chart.tsx +355 -0
- package/assets/power-apps-code-app/3438c352483e40982f6c5c0fc36fd71f8e7adbbb/starter/src/components/ui/checkbox.tsx +32 -0
- package/assets/power-apps-code-app/3438c352483e40982f6c5c0fc36fd71f8e7adbbb/starter/src/components/ui/command.tsx +184 -0
- package/assets/power-apps-code-app/3438c352483e40982f6c5c0fc36fd71f8e7adbbb/starter/src/components/ui/dialog.tsx +141 -0
- package/assets/power-apps-code-app/3438c352483e40982f6c5c0fc36fd71f8e7adbbb/starter/src/components/ui/dropdown-menu.tsx +257 -0
- package/assets/power-apps-code-app/3438c352483e40982f6c5c0fc36fd71f8e7adbbb/starter/src/components/ui/input.tsx +21 -0
- package/assets/power-apps-code-app/3438c352483e40982f6c5c0fc36fd71f8e7adbbb/starter/src/components/ui/label.tsx +22 -0
- package/assets/power-apps-code-app/3438c352483e40982f6c5c0fc36fd71f8e7adbbb/starter/src/components/ui/popover.tsx +46 -0
- package/assets/power-apps-code-app/3438c352483e40982f6c5c0fc36fd71f8e7adbbb/starter/src/components/ui/progress.tsx +29 -0
- package/assets/power-apps-code-app/3438c352483e40982f6c5c0fc36fd71f8e7adbbb/starter/src/components/ui/select.tsx +185 -0
- package/assets/power-apps-code-app/3438c352483e40982f6c5c0fc36fd71f8e7adbbb/starter/src/components/ui/separator.tsx +26 -0
- package/assets/power-apps-code-app/3438c352483e40982f6c5c0fc36fd71f8e7adbbb/starter/src/components/ui/skeleton.tsx +13 -0
- package/assets/power-apps-code-app/3438c352483e40982f6c5c0fc36fd71f8e7adbbb/starter/src/components/ui/table.tsx +114 -0
- package/assets/power-apps-code-app/3438c352483e40982f6c5c0fc36fd71f8e7adbbb/starter/src/components/ui/tabs.tsx +66 -0
- package/assets/power-apps-code-app/3438c352483e40982f6c5c0fc36fd71f8e7adbbb/starter/src/components/ui/textarea.tsx +18 -0
- package/assets/power-apps-code-app/3438c352483e40982f6c5c0fc36fd71f8e7adbbb/starter/src/components/ui/tooltip.tsx +61 -0
- package/assets/power-apps-code-app/3438c352483e40982f6c5c0fc36fd71f8e7adbbb/starter/src/hooks/use-theme.ts +11 -0
- package/assets/power-apps-code-app/3438c352483e40982f6c5c0fc36fd71f8e7adbbb/starter/src/index.css +131 -0
- package/assets/power-apps-code-app/3438c352483e40982f6c5c0fc36fd71f8e7adbbb/starter/src/lib/utils.ts +6 -0
- package/assets/power-apps-code-app/3438c352483e40982f6c5c0fc36fd71f8e7adbbb/starter/src/main.tsx +10 -0
- package/assets/power-apps-code-app/3438c352483e40982f6c5c0fc36fd71f8e7adbbb/starter/src/pages/_layout.tsx +31 -0
- package/assets/power-apps-code-app/3438c352483e40982f6c5c0fc36fd71f8e7adbbb/starter/src/pages/home.tsx +59 -0
- package/assets/power-apps-code-app/3438c352483e40982f6c5c0fc36fd71f8e7adbbb/starter/src/pages/not-found.tsx +14 -0
- package/assets/power-apps-code-app/3438c352483e40982f6c5c0fc36fd71f8e7adbbb/starter/src/providers/query-provider.tsx +26 -0
- package/assets/power-apps-code-app/3438c352483e40982f6c5c0fc36fd71f8e7adbbb/starter/src/providers/sonner-provider.tsx +23 -0
- package/assets/power-apps-code-app/3438c352483e40982f6c5c0fc36fd71f8e7adbbb/starter/src/providers/theme-provider.tsx +64 -0
- package/assets/power-apps-code-app/3438c352483e40982f6c5c0fc36fd71f8e7adbbb/starter/src/router.tsx +24 -0
- package/assets/power-apps-code-app/3438c352483e40982f6c5c0fc36fd71f8e7adbbb/starter/tsconfig.app.json +34 -0
- package/assets/power-apps-code-app/3438c352483e40982f6c5c0fc36fd71f8e7adbbb/starter/tsconfig.json +13 -0
- package/assets/power-apps-code-app/3438c352483e40982f6c5c0fc36fd71f8e7adbbb/starter/tsconfig.node.json +26 -0
- package/assets/power-apps-code-app/3438c352483e40982f6c5c0fc36fd71f8e7adbbb/starter/vite.config.ts +19 -0
- package/assets/power-apps-code-app/README.md +27 -0
- package/dist/args.js +7 -1
- package/dist/args.js.map +1 -1
- package/dist/catalogs.d.ts +3 -1
- package/dist/catalogs.js +23 -0
- package/dist/catalogs.js.map +1 -1
- package/dist/code-apps-plugin.d.ts +13 -0
- package/dist/code-apps-plugin.js +69 -0
- package/dist/code-apps-plugin.js.map +1 -0
- package/dist/commands.js +282 -55
- package/dist/commands.js.map +1 -1
- package/dist/file-system.d.ts +16 -1
- package/dist/file-system.js +320 -10
- package/dist/file-system.js.map +1 -1
- package/dist/genai-templates.d.ts +6 -6
- package/dist/genai-templates.js +0 -3
- package/dist/genai-templates.js.map +1 -1
- package/dist/interactive.d.ts +40 -3
- package/dist/interactive.js +180 -24
- package/dist/interactive.js.map +1 -1
- package/dist/migrate-plan.d.ts +4 -4
- package/dist/migrate-plan.js +1 -1
- package/dist/migrate-plan.js.map +1 -1
- package/dist/planner.js +177 -40
- package/dist/planner.js.map +1 -1
- package/dist/power-apps-assets.d.ts +30 -0
- package/dist/power-apps-assets.js +131 -0
- package/dist/power-apps-assets.js.map +1 -0
- package/dist/power-apps-templates.d.ts +3 -0
- package/dist/power-apps-templates.js +140 -0
- package/dist/power-apps-templates.js.map +1 -0
- package/dist/power-apps-validation.d.ts +1 -0
- package/dist/power-apps-validation.js +79 -0
- package/dist/power-apps-validation.js.map +1 -0
- package/dist/project-dependencies.d.ts +1 -0
- package/dist/project-dependencies.js +12 -1
- package/dist/project-dependencies.js.map +1 -1
- package/dist/standard-templates.d.ts +4 -4
- package/dist/standard-templates.js.map +1 -1
- package/dist/templates.d.ts +2 -2
- package/dist/templates.js +196 -50
- package/dist/templates.js.map +1 -1
- package/dist/types.d.ts +73 -15
- package/dist/workstation.d.ts +10 -5
- package/dist/workstation.js +25 -10
- package/dist/workstation.js.map +1 -1
- package/docs/assets/liftoff-terminal.svg +33 -0
- package/docs/azure-deployment.md +66 -0
- package/docs/cli-reference.md +128 -0
- package/docs/configuration-and-manifests.md +102 -0
- package/docs/existing-repositories.md +87 -0
- package/docs/getting-started.md +114 -0
- package/docs/prerequisites.md +99 -0
- package/docs/project-structure.md +119 -0
- package/docs/safety-and-consent.md +98 -0
- package/docs/spec-workflows-and-agents.md +92 -0
- package/docs/troubleshooting.md +133 -0
- package/docs/workloads.md +128 -0
- package/package.json +8 -2
package/dist/commands.js
CHANGED
|
@@ -6,14 +6,15 @@ import os from 'node:os';
|
|
|
6
6
|
import path from 'node:path';
|
|
7
7
|
import { getCommandHelp, getGeneralHelp, readBooleanFlag, readListFlag, readStringFlag } from './args.js';
|
|
8
8
|
import { getCodingAgent, getFrameworkDefinition, getSpecWorkflow, listRegions, patterns, providers, searchRegions } from './catalogs.js';
|
|
9
|
+
import { probeCodeAppsPlugin } from './code-apps-plugin.js';
|
|
9
10
|
import { initializeFramework } from './framework-adapters.js';
|
|
10
11
|
import { validateFrameworkInstallation } from './framework-validation.js';
|
|
11
|
-
import { artifactPath,
|
|
12
|
-
import { InteractivePrompter } from './interactive.js';
|
|
12
|
+
import { artifactPath, applyProjectFileTransaction, assertNewOrEmptyDirectory, findProjectRoot, loadManifest, manifestDisplayPath, resolveProjectPath, validateGeneratedProject, writeArtifacts, writeProjectFile } from './file-system.js';
|
|
13
|
+
import { InteractiveCancelledError, InteractivePrompter } from './interactive.js';
|
|
13
14
|
import { applyMergePreflight, assertSafeInitTarget, authorizeMergePreflight, buildMergePreflight, captureTreeState, discoverGitRoot, resolveInitTargetFromDiscovery, validateStagedTree, withStagingArea, writeStagedArtifacts } from './init-filesystem.js';
|
|
14
15
|
import { renderMigrationChecklist, renderMigrationProposal, renderMigrationTasks, seedMigrationGroups } from './migrate-plan.js';
|
|
15
16
|
import { buildProjectPlan, loadConfigOptions, mergeOptions, PlanValidationError, projectPlanEntries } from './planner.js';
|
|
16
|
-
import { buildDependencySetupPlan, runDependencySetup } from './project-dependencies.js';
|
|
17
|
+
import { buildDependencySetupPlan, dependencyResumeCommand, runDependencySetup, verifyPowerAppsPackageMetadata } from './project-dependencies.js';
|
|
17
18
|
import { formatCommand, NodeCommandRunner } from './process-runner.js';
|
|
18
19
|
import { scanDefaults, scanLegacyProject } from './scan.js';
|
|
19
20
|
import { hasDrift, reconcileProject } from './reconcile.js';
|
|
@@ -24,7 +25,9 @@ import { liftoffVersion } from './version.js';
|
|
|
24
25
|
import { blockingReadinessFailures, detectHostEnvironment, installRequirement, probeWorkstation, selectLiftoffRuntimeRequirements, selectWorkstationRequirements } from './workstation.js';
|
|
25
26
|
export async function runCommand(parsed, context) {
|
|
26
27
|
const helpRequested = parsed.command !== undefined && readBooleanFlag(parsed.flags, 'help') === true;
|
|
27
|
-
const jsonMode = !helpRequested && (parsed.command === 'doctor' ||
|
|
28
|
+
const jsonMode = !helpRequested && (parsed.command === 'doctor' ||
|
|
29
|
+
parsed.command === 'update' ||
|
|
30
|
+
parsed.command === 'validate') &&
|
|
28
31
|
readBooleanFlag(parsed.flags, 'json') === true;
|
|
29
32
|
const presentation = new PresentationSession({
|
|
30
33
|
stdout: context.stdout,
|
|
@@ -80,6 +83,10 @@ export async function runCommand(parsed, context) {
|
|
|
80
83
|
}
|
|
81
84
|
}
|
|
82
85
|
catch (error) {
|
|
86
|
+
if (error instanceof InteractiveCancelledError) {
|
|
87
|
+
presentation.cancellation('Interactive operation stopped.');
|
|
88
|
+
return 0;
|
|
89
|
+
}
|
|
83
90
|
if (error instanceof PlanValidationError) {
|
|
84
91
|
presentation.error(error.issues.join('\n'), parsed.command ? `Run \`liftoff ${parsed.command} --help\` to review accepted values.` : undefined);
|
|
85
92
|
return 1;
|
|
@@ -103,7 +110,10 @@ async function initCommand(parsed, context) {
|
|
|
103
110
|
? new InteractivePrompter({
|
|
104
111
|
input: context.stdin,
|
|
105
112
|
output: context.stdout,
|
|
106
|
-
presentation
|
|
113
|
+
presentation,
|
|
114
|
+
cwd: context.cwd,
|
|
115
|
+
configuredRoot: git.exact ? git.root : undefined,
|
|
116
|
+
runner
|
|
107
117
|
})
|
|
108
118
|
: undefined;
|
|
109
119
|
try {
|
|
@@ -111,12 +121,24 @@ async function initCommand(parsed, context) {
|
|
|
111
121
|
if (needsPrompts) {
|
|
112
122
|
presentation.stage('Configure project');
|
|
113
123
|
}
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
:
|
|
124
|
+
let options;
|
|
125
|
+
let plan;
|
|
126
|
+
let confirmed;
|
|
127
|
+
try {
|
|
128
|
+
options = needsPrompts ? await prompter.promptForInitOptions(initial) : initial;
|
|
129
|
+
plan = buildProjectPlan(options, { requireProjectName: true });
|
|
130
|
+
presentation.stage('Review resolved plan');
|
|
131
|
+
confirmed = options.yes === true
|
|
132
|
+
? (presentation.definitions('Resolved project plan', projectPlanEntries(plan)), true)
|
|
133
|
+
: await prompter.confirmPlan(plan);
|
|
134
|
+
}
|
|
135
|
+
catch (error) {
|
|
136
|
+
if (error instanceof InteractiveCancelledError) {
|
|
137
|
+
presentation.cancellation('Initialization stopped; no destination files were changed.');
|
|
138
|
+
return 0;
|
|
139
|
+
}
|
|
140
|
+
throw error;
|
|
141
|
+
}
|
|
120
142
|
if (!confirmed) {
|
|
121
143
|
presentation.cancellation('Initialization stopped; no destination files were changed.');
|
|
122
144
|
return 0;
|
|
@@ -175,7 +197,8 @@ async function initCommand(parsed, context) {
|
|
|
175
197
|
}
|
|
176
198
|
presentation.bullets('Configured integrations', [
|
|
177
199
|
`${plan.specWorkflow.label} ${plan.framework.version}`,
|
|
178
|
-
...plan.agents.map((agent) => `${agent.label}${plan.defaultAgent?.id === agent.id ? ' (default)' : ''}`)
|
|
200
|
+
...plan.agents.map((agent) => `${agent.label}${plan.defaultAgent?.id === agent.id ? ' (default)' : ''}`),
|
|
201
|
+
...readiness.pluginProbes.map((probe) => `Code Apps plugin for ${probe.agent.label}: ${probe.state}`)
|
|
179
202
|
]);
|
|
180
203
|
if (readiness.deferred.length > 0) {
|
|
181
204
|
presentation.bullets('Deferred advisory checks', readiness.deferred);
|
|
@@ -221,13 +244,22 @@ async function planCommand(parsed, context) {
|
|
|
221
244
|
}
|
|
222
245
|
async function ensureWorkstationReady(plan, options, context, runner, presentation, prompter, resumeInvocation = 'liftoff init', commandCwd) {
|
|
223
246
|
const requirements = selectWorkstationRequirements(plan);
|
|
224
|
-
|
|
247
|
+
const [initialProbes, pluginProbes] = await Promise.all([
|
|
248
|
+
probeWorkstation(requirements, runner, { cwd: commandCwd }),
|
|
249
|
+
plan.workload === 'power-apps-code-app' && plan.codeAppsPlugin
|
|
250
|
+
? probeCodeAppsPlugin(plan.agents, runner, commandCwd)
|
|
251
|
+
: Promise.resolve([])
|
|
252
|
+
]);
|
|
253
|
+
let probes = initialProbes;
|
|
225
254
|
presentation.table('Workstation readiness', ['Requirement', 'Level', 'State', 'Detail'], probes.map((probe) => [
|
|
226
255
|
probe.requirement.definition.label,
|
|
227
256
|
probe.requirement.severity,
|
|
228
257
|
probe.state,
|
|
229
258
|
probe.detail
|
|
230
259
|
]));
|
|
260
|
+
if (pluginProbes.length > 0) {
|
|
261
|
+
presentation.table('Optional Code Apps plugin', ['Agent', 'State', 'Detail'], pluginProbes.map((probe) => [probe.agent.label, probe.state, probe.detail]));
|
|
262
|
+
}
|
|
231
263
|
const actionable = probes.filter((probe) => probe.state !== 'ready');
|
|
232
264
|
const host = await detectHostEnvironment();
|
|
233
265
|
const installInstruction = (probe) => {
|
|
@@ -311,7 +343,7 @@ async function ensureWorkstationReady(plan, options, context, runner, presentati
|
|
|
311
343
|
presentation.error('Workstation readiness is incomplete.', options.installTools
|
|
312
344
|
? `Open a new terminal if PATH changed, then rerun \`${resumeInvocation}\` with the same project options.`
|
|
313
345
|
: `Resume with \`${resumeInvocation} --install-tools\` plus the same project options after reviewing the commands.`);
|
|
314
|
-
return { ready: false, deferred: [], probes };
|
|
346
|
+
return { ready: false, deferred: [], probes, pluginProbes };
|
|
315
347
|
}
|
|
316
348
|
const deferred = [
|
|
317
349
|
...probes
|
|
@@ -319,9 +351,12 @@ async function ensureWorkstationReady(plan, options, context, runner, presentati
|
|
|
319
351
|
.map((probe) => `${probe.requirement.definition.label}: ${probe.detail} Remedy: ${installInstruction(probe)}`),
|
|
320
352
|
...probes.flatMap((probe) => probe.notices
|
|
321
353
|
.filter((notice) => notice.state !== 'ready')
|
|
322
|
-
.map((notice) => `${notice.label}: ${notice.detail}${notice.remedy ? ` Remedy: ${notice.remedy}` : ''}`))
|
|
354
|
+
.map((notice) => `${notice.label}: ${notice.detail}${notice.remedy ? ` Remedy: ${notice.remedy}` : ''}`)),
|
|
355
|
+
...pluginProbes
|
|
356
|
+
.filter((probe) => probe.state !== 'ready')
|
|
357
|
+
.map((probe) => `${probe.agent.label} Code Apps plugin: ${probe.detail}${probe.remedy ? ` Remedy: ${probe.remedy}` : ''}`)
|
|
323
358
|
];
|
|
324
|
-
return { ready: true, deferred, probes };
|
|
359
|
+
return { ready: true, deferred, probes, pluginProbes };
|
|
325
360
|
}
|
|
326
361
|
async function handleProjectDependencies(plan, projectRoot, options, probes, context, runner, presentation, prompter) {
|
|
327
362
|
const dependencyPlan = buildDependencySetupPlan(plan, projectRoot, probes);
|
|
@@ -334,9 +369,16 @@ async function handleProjectDependencies(plan, projectRoot, options, probes, con
|
|
|
334
369
|
if (!installDependencies) {
|
|
335
370
|
return {
|
|
336
371
|
success: true,
|
|
337
|
-
deferred: dependencyPlan.commands.map((command) => `${command.label}: ${
|
|
372
|
+
deferred: dependencyPlan.commands.map((command) => `${command.label}: ${dependencyResumeCommand(command)}`)
|
|
338
373
|
};
|
|
339
374
|
}
|
|
375
|
+
if (plan.workload === 'power-apps-code-app') {
|
|
376
|
+
const issues = await verifyPowerAppsPackageMetadata(projectRoot);
|
|
377
|
+
if (issues.length > 0) {
|
|
378
|
+
presentation.error('Power Apps dependency installation blocked', `${issues.join(' ')} Restore package.json and package-lock.json or run \`liftoff update --apply\`.`);
|
|
379
|
+
return { success: false, deferred: [] };
|
|
380
|
+
}
|
|
381
|
+
}
|
|
340
382
|
presentation.stage('Install project dependencies');
|
|
341
383
|
const result = await runDependencySetup(dependencyPlan, projectRoot, runner, {
|
|
342
384
|
...presentation.childStreams(),
|
|
@@ -387,11 +429,21 @@ function regionsCommand(parsed, context) {
|
|
|
387
429
|
}
|
|
388
430
|
async function validateCommand(parsed, context) {
|
|
389
431
|
context.presentation.commandIdentity('validate', 'Validate a generated Liftoff project');
|
|
432
|
+
const jsonMode = readBooleanFlag(parsed.flags, 'json') ?? false;
|
|
390
433
|
const explicit = parsed.positional[0] ?? readStringFlag(parsed.flags, 'project');
|
|
391
434
|
const projectRoot = explicit
|
|
392
435
|
? path.resolve(context.cwd, explicit)
|
|
393
436
|
: (await findProjectRoot(context.cwd)) ?? context.cwd;
|
|
394
437
|
const issues = await validateGeneratedProject(projectRoot);
|
|
438
|
+
if (jsonMode) {
|
|
439
|
+
context.presentation.rawStdout(`${JSON.stringify({
|
|
440
|
+
schemaVersion: 1,
|
|
441
|
+
projectRoot,
|
|
442
|
+
valid: issues.length === 0,
|
|
443
|
+
issues
|
|
444
|
+
}, null, 2)}\n`);
|
|
445
|
+
return issues.length === 0 ? 0 : 1;
|
|
446
|
+
}
|
|
395
447
|
if (issues.length > 0) {
|
|
396
448
|
context.presentation.error(issues.join('\n'), 'Restore invalid generated files or the manifest from version control, then rerun validation.');
|
|
397
449
|
return 1;
|
|
@@ -534,12 +586,15 @@ async function executeMigration(parsed, context, sourceRoot) {
|
|
|
534
586
|
if (flagOptions.projectType === 'genai' && flagOptions.apiStack === undefined) {
|
|
535
587
|
initial.apiStack = undefined;
|
|
536
588
|
}
|
|
589
|
+
const runner = context.runner ?? new NodeCommandRunner();
|
|
537
590
|
const interactive = initial.yes !== true;
|
|
538
591
|
const prompter = interactive
|
|
539
592
|
? new InteractivePrompter({
|
|
540
593
|
input: context.stdin,
|
|
541
594
|
output: context.stdout,
|
|
542
|
-
presentation
|
|
595
|
+
presentation,
|
|
596
|
+
cwd: context.cwd,
|
|
597
|
+
runner
|
|
543
598
|
})
|
|
544
599
|
: undefined;
|
|
545
600
|
try {
|
|
@@ -549,6 +604,10 @@ async function executeMigration(parsed, context, sourceRoot) {
|
|
|
549
604
|
}
|
|
550
605
|
const options = needsPrompts ? await prompter.promptForInitOptions(initial) : initial;
|
|
551
606
|
const plan = buildProjectPlan(options, { requireProjectName: true });
|
|
607
|
+
if (plan.workload === 'power-apps-code-app') {
|
|
608
|
+
presentation.error('Power Apps code app migration is not supported.', 'Initialize a fresh Power Apps code app, then move application changes intentionally.');
|
|
609
|
+
return 1;
|
|
610
|
+
}
|
|
552
611
|
presentation.stage('Review migration plan');
|
|
553
612
|
const confirmed = options.yes === true
|
|
554
613
|
? (presentation.definitions('Resolved migration plan', projectPlanEntries(plan)), true)
|
|
@@ -566,7 +625,6 @@ async function executeMigration(parsed, context, sourceRoot) {
|
|
|
566
625
|
const target = { root: targetRoot, mode: 'named-child' };
|
|
567
626
|
await assertSafeInitTarget(target, parentDir);
|
|
568
627
|
await assertNewOrEmptyDirectory(targetRoot);
|
|
569
|
-
const runner = context.runner ?? new NodeCommandRunner();
|
|
570
628
|
presentation.stage('Check workstation readiness');
|
|
571
629
|
const readinessRoot = await mkdtemp(path.join(os.tmpdir(), 'liftoff-migrate-readiness-'));
|
|
572
630
|
const readiness = await (async () => {
|
|
@@ -655,6 +713,11 @@ async function migrateCommand(parsed, context) {
|
|
|
655
713
|
context.presentation.error('Usage: liftoff migrate <path-to-existing-project>', 'Run `liftoff migrate --help` for accepted migration options.');
|
|
656
714
|
return 1;
|
|
657
715
|
}
|
|
716
|
+
const migrationOptions = await optionsFromParsedArgs(parsed, context.cwd, false);
|
|
717
|
+
if (migrationOptions.projectType === 'power-apps-code-app') {
|
|
718
|
+
context.presentation.error('Power Apps code app migration is not supported.', 'Initialize a fresh Power Apps code app with `liftoff init --type power-apps-code-app`, then move application changes intentionally.');
|
|
719
|
+
return 1;
|
|
720
|
+
}
|
|
658
721
|
const sourceRoot = path.resolve(context.cwd, sourceArg);
|
|
659
722
|
let sourceDetails;
|
|
660
723
|
try {
|
|
@@ -765,17 +828,35 @@ async function updateCommand(parsed, context) {
|
|
|
765
828
|
}
|
|
766
829
|
const config = await loadConfigOptions('liftoff.config.json', projectRoot);
|
|
767
830
|
const plan = buildProjectPlan(config, { requireProjectName: true });
|
|
768
|
-
|
|
769
|
-
|
|
831
|
+
const recordedWorkload = manifest.project.workload;
|
|
832
|
+
if (plan.workload !== recordedWorkload.kind) {
|
|
833
|
+
const involvesPowerApps = plan.workload === 'power-apps-code-app' ||
|
|
834
|
+
recordedWorkload.kind === 'power-apps-code-app';
|
|
835
|
+
presentation.error(`Project type changes (${recordedWorkload.kind} -> ${plan.workload}) are not supported by update.`, involvesPowerApps
|
|
836
|
+
? 'Initialize a fresh project for the new workload.'
|
|
837
|
+
: 'Run `liftoff migrate` instead.');
|
|
770
838
|
return 1;
|
|
771
839
|
}
|
|
772
|
-
if (plan.
|
|
773
|
-
|
|
774
|
-
|
|
840
|
+
if (plan.workload === 'power-apps-code-app' && recordedWorkload.kind === 'power-apps-code-app') {
|
|
841
|
+
const recordedStarter = recordedWorkload.starter;
|
|
842
|
+
if (plan.starter.repository !== recordedStarter.repository ||
|
|
843
|
+
plan.starter.path !== recordedStarter.path ||
|
|
844
|
+
plan.starter.commit !== recordedStarter.commit) {
|
|
845
|
+
presentation.error('The Power Apps starter repository, path, or commit differs from the recorded immutable source.', 'Restore the recorded source identity or initialize a fresh project.');
|
|
846
|
+
return 1;
|
|
847
|
+
}
|
|
775
848
|
}
|
|
776
|
-
if (plan.
|
|
777
|
-
|
|
778
|
-
|
|
849
|
+
else if (plan.workload !== 'power-apps-code-app' && recordedWorkload.kind !== 'power-apps-code-app') {
|
|
850
|
+
if (plan.apiStack.id !== recordedWorkload.apiStack) {
|
|
851
|
+
presentation.error(`API stack changes (${recordedWorkload.apiStack} -> ${plan.apiStack.id}) are a migration, not an update.`, 'Run `liftoff migrate` instead.');
|
|
852
|
+
return 1;
|
|
853
|
+
}
|
|
854
|
+
const recordedPattern = recordedWorkload.kind === 'genai' ? recordedWorkload.pattern : undefined;
|
|
855
|
+
const desiredPattern = plan.workload === 'genai' ? plan.pattern.id : undefined;
|
|
856
|
+
if (desiredPattern !== recordedPattern) {
|
|
857
|
+
presentation.error(`Pattern changes (${recordedPattern ?? 'none'} -> ${desiredPattern ?? 'none'}) are a migration, not an update.`, 'Run `liftoff migrate` instead.');
|
|
858
|
+
return 1;
|
|
859
|
+
}
|
|
779
860
|
}
|
|
780
861
|
if (plan.specWorkflow.id !== manifest.project.specWorkflow) {
|
|
781
862
|
presentation.error(`Spec workflow changes (${manifest.project.specWorkflow} -> ${plan.specWorkflow.id}) require official framework initialization and are not supported by liftoff update.`, 'Restore the workflow recorded in liftoff.manifest.json or migrate into a fresh project.');
|
|
@@ -837,20 +918,29 @@ async function updateCommand(parsed, context) {
|
|
|
837
918
|
await preflightUpdate(projectRoot, entries, force);
|
|
838
919
|
const written = [];
|
|
839
920
|
const skipped = [];
|
|
921
|
+
const mutations = [];
|
|
840
922
|
for (const entry of entries) {
|
|
841
923
|
switch (entry.status) {
|
|
842
924
|
case 'new':
|
|
843
925
|
case 'missing':
|
|
844
926
|
case 'upgrade':
|
|
845
|
-
|
|
927
|
+
mutations.push({
|
|
928
|
+
type: 'write',
|
|
929
|
+
pathParts: entry.pathParts,
|
|
930
|
+
content: entry.rendered.content
|
|
931
|
+
});
|
|
846
932
|
written.push(entry);
|
|
847
933
|
break;
|
|
848
934
|
case 'moved':
|
|
849
935
|
if (entry.cleanMove || force) {
|
|
850
936
|
if (!entry.destinationMatches) {
|
|
851
|
-
|
|
937
|
+
mutations.push({
|
|
938
|
+
type: 'write',
|
|
939
|
+
pathParts: entry.pathParts,
|
|
940
|
+
content: entry.rendered.content
|
|
941
|
+
});
|
|
852
942
|
}
|
|
853
|
-
|
|
943
|
+
mutations.push({ type: 'delete', pathParts: entry.previousPathParts });
|
|
854
944
|
written.push(entry);
|
|
855
945
|
}
|
|
856
946
|
else {
|
|
@@ -859,9 +949,13 @@ async function updateCommand(parsed, context) {
|
|
|
859
949
|
break;
|
|
860
950
|
case 'conflict':
|
|
861
951
|
if (force) {
|
|
862
|
-
|
|
952
|
+
mutations.push({
|
|
953
|
+
type: 'write',
|
|
954
|
+
pathParts: entry.pathParts,
|
|
955
|
+
content: entry.rendered.content
|
|
956
|
+
});
|
|
863
957
|
if (entry.previousPathParts) {
|
|
864
|
-
|
|
958
|
+
mutations.push({ type: 'delete', pathParts: entry.previousPathParts });
|
|
865
959
|
}
|
|
866
960
|
written.push(entry);
|
|
867
961
|
}
|
|
@@ -904,7 +998,12 @@ async function updateCommand(parsed, context) {
|
|
|
904
998
|
nextManifest.artifacts.push(oldByName.get(entry.logicalName));
|
|
905
999
|
}
|
|
906
1000
|
}
|
|
907
|
-
|
|
1001
|
+
mutations.push({
|
|
1002
|
+
type: 'write',
|
|
1003
|
+
pathParts: ['liftoff.manifest.json'],
|
|
1004
|
+
content: `${JSON.stringify(nextManifest, null, 2)}\n`
|
|
1005
|
+
});
|
|
1006
|
+
await applyProjectFileTransaction(projectRoot, mutations);
|
|
908
1007
|
if (jsonMode) {
|
|
909
1008
|
presentation.rawStdout(`${JSON.stringify({
|
|
910
1009
|
schemaVersion: 1,
|
|
@@ -996,11 +1095,17 @@ function workstationLayer(probes) {
|
|
|
996
1095
|
}
|
|
997
1096
|
function workstationSelectionFromManifest(manifest) {
|
|
998
1097
|
const framework = getFrameworkDefinition(manifest.project.specWorkflow);
|
|
1098
|
+
const workload = manifest.project.workload;
|
|
999
1099
|
return {
|
|
1000
|
-
|
|
1100
|
+
workload: workload.kind === 'power-apps-code-app'
|
|
1101
|
+
? { kind: 'power-apps-code-app' }
|
|
1102
|
+
: {
|
|
1103
|
+
kind: workload.kind,
|
|
1104
|
+
apiStack: { id: workload.apiStack },
|
|
1105
|
+
provider: { id: workload.cloud }
|
|
1106
|
+
},
|
|
1001
1107
|
specWorkflow: { id: manifest.project.specWorkflow },
|
|
1002
1108
|
framework: { version: framework.version },
|
|
1003
|
-
provider: { id: manifest.project.cloud },
|
|
1004
1109
|
agents: manifest.framework.state === 'initialized'
|
|
1005
1110
|
? manifest.project.agents.map((id) => {
|
|
1006
1111
|
const agent = getCodingAgent(id);
|
|
@@ -1106,6 +1211,26 @@ function stackProjectCheck(projectRoot, apiStack) {
|
|
|
1106
1211
|
remedy: `repair the generated ${apiStack} backend configuration`
|
|
1107
1212
|
};
|
|
1108
1213
|
}
|
|
1214
|
+
async function powerAppsProjectCheck(projectRoot) {
|
|
1215
|
+
const issues = await verifyPowerAppsPackageMetadata(projectRoot);
|
|
1216
|
+
if (issues.length === 0) {
|
|
1217
|
+
return {
|
|
1218
|
+
id: 'power-apps-project',
|
|
1219
|
+
label: 'Power Apps project',
|
|
1220
|
+
severity: 'ok',
|
|
1221
|
+
state: 'ready',
|
|
1222
|
+
detail: 'SDK, Vite plugin, and deterministic lockfile metadata are valid'
|
|
1223
|
+
};
|
|
1224
|
+
}
|
|
1225
|
+
return {
|
|
1226
|
+
id: 'power-apps-project',
|
|
1227
|
+
label: 'Power Apps project',
|
|
1228
|
+
severity: 'fail',
|
|
1229
|
+
state: 'unhealthy',
|
|
1230
|
+
detail: issues[0],
|
|
1231
|
+
remedy: 'restore package.json and package-lock.json or run liftoff update --apply'
|
|
1232
|
+
};
|
|
1233
|
+
}
|
|
1109
1234
|
function runReadOnly(command, args, cwd) {
|
|
1110
1235
|
const result = spawnSync(command, args, { cwd, encoding: 'utf8' });
|
|
1111
1236
|
return { status: result.status, stdout: result.stdout, stderr: result.stderr };
|
|
@@ -1204,7 +1329,20 @@ async function projectLayer(projectRoot, manifest) {
|
|
|
1204
1329
|
checks.push({ label: 'version', severity: 'ok', detail: `generated by ${manifest.liftoffVersion}, CLI ${liftoffVersion}` });
|
|
1205
1330
|
}
|
|
1206
1331
|
checks.push(...await frameworkDoctorChecks(projectRoot, manifest));
|
|
1207
|
-
|
|
1332
|
+
if (manifest.project.workload.kind === 'power-apps-code-app') {
|
|
1333
|
+
const starter = manifest.project.workload.starter;
|
|
1334
|
+
checks.push({
|
|
1335
|
+
id: 'power-apps-starter',
|
|
1336
|
+
label: 'Power Apps starter',
|
|
1337
|
+
severity: 'ok',
|
|
1338
|
+
state: 'ready',
|
|
1339
|
+
detail: `${starter.repository}/${starter.path} @ ${starter.commit}`
|
|
1340
|
+
});
|
|
1341
|
+
checks.push(await powerAppsProjectCheck(projectRoot));
|
|
1342
|
+
}
|
|
1343
|
+
else {
|
|
1344
|
+
checks.push(stackProjectCheck(projectRoot, manifest.project.workload.apiStack));
|
|
1345
|
+
}
|
|
1208
1346
|
try {
|
|
1209
1347
|
const config = await loadConfigOptions('liftoff.config.json', projectRoot);
|
|
1210
1348
|
const plan = buildProjectPlan(config, { requireProjectName: true });
|
|
@@ -1233,8 +1371,40 @@ async function projectLayer(projectRoot, manifest) {
|
|
|
1233
1371
|
}
|
|
1234
1372
|
return { title: 'Project', checks };
|
|
1235
1373
|
}
|
|
1236
|
-
async function runtimeLayer(projectRoot, dockerAvailable, runner) {
|
|
1374
|
+
async function runtimeLayer(projectRoot, dockerAvailable, runner, manifest) {
|
|
1237
1375
|
const checks = [];
|
|
1376
|
+
if (manifest.project.workload.kind === 'power-apps-code-app') {
|
|
1377
|
+
if (!existsSync(path.join(projectRoot, 'node_modules'))) {
|
|
1378
|
+
checks.push({
|
|
1379
|
+
id: 'power-apps-cli',
|
|
1380
|
+
label: 'Power Apps CLI',
|
|
1381
|
+
severity: 'skipped',
|
|
1382
|
+
state: 'not-observable',
|
|
1383
|
+
detail: 'project dependencies are not installed',
|
|
1384
|
+
remedy: 'run npm ci, then rerun liftoff doctor'
|
|
1385
|
+
});
|
|
1386
|
+
return { title: 'Runtime', checks };
|
|
1387
|
+
}
|
|
1388
|
+
const executable = process.platform === 'win32' ? 'npx.cmd' : 'npx';
|
|
1389
|
+
const result = await runner.run({ executable, args: ['--no-install', 'power-apps', '--version'] }, { cwd: projectRoot, timeoutMs: 15_000 });
|
|
1390
|
+
checks.push(result.status === 0
|
|
1391
|
+
? {
|
|
1392
|
+
id: 'power-apps-cli',
|
|
1393
|
+
label: 'Power Apps CLI',
|
|
1394
|
+
severity: 'ok',
|
|
1395
|
+
state: 'ready',
|
|
1396
|
+
detail: result.stdout.trim() || 'local CLI is available'
|
|
1397
|
+
}
|
|
1398
|
+
: {
|
|
1399
|
+
id: 'power-apps-cli',
|
|
1400
|
+
label: 'Power Apps CLI',
|
|
1401
|
+
severity: 'fail',
|
|
1402
|
+
state: 'unhealthy',
|
|
1403
|
+
detail: (result.stderr || 'local CLI probe failed').trim().split('\n')[0],
|
|
1404
|
+
remedy: 'run npm ci and verify @microsoft/power-apps before binding the environment'
|
|
1405
|
+
});
|
|
1406
|
+
return { title: 'Runtime', checks };
|
|
1407
|
+
}
|
|
1238
1408
|
if (existsSync(path.join(projectRoot, '.env.example'))) {
|
|
1239
1409
|
if (existsSync(path.join(projectRoot, '.env'))) {
|
|
1240
1410
|
checks.push({ label: '.env', severity: 'ok', detail: 'present' });
|
|
@@ -1268,6 +1438,28 @@ async function runtimeLayer(projectRoot, dockerAvailable, runner) {
|
|
|
1268
1438
|
}
|
|
1269
1439
|
return { title: 'Runtime', checks };
|
|
1270
1440
|
}
|
|
1441
|
+
async function codeAppsPluginLayer(projectRoot, manifest, runner) {
|
|
1442
|
+
const agents = manifest.project.agents.map((id) => {
|
|
1443
|
+
const agent = getCodingAgent(id);
|
|
1444
|
+
if (!agent) {
|
|
1445
|
+
throw new Error(`Manifest references unknown coding agent ${id}.`);
|
|
1446
|
+
}
|
|
1447
|
+
return agent;
|
|
1448
|
+
});
|
|
1449
|
+
const probes = await probeCodeAppsPlugin(agents, runner, projectRoot);
|
|
1450
|
+
return {
|
|
1451
|
+
title: 'Optional Code Apps plugin',
|
|
1452
|
+
checks: probes.map((probe) => ({
|
|
1453
|
+
id: probe.id,
|
|
1454
|
+
label: probe.agent.label,
|
|
1455
|
+
severity: probe.state === 'ready' ? 'ok' : 'warn',
|
|
1456
|
+
state: probe.state,
|
|
1457
|
+
requirementSeverity: 'advisory',
|
|
1458
|
+
detail: probe.detail,
|
|
1459
|
+
...(probe.remedy ? { remedy: probe.remedy } : {})
|
|
1460
|
+
}))
|
|
1461
|
+
};
|
|
1462
|
+
}
|
|
1271
1463
|
function renderDoctorLayers(layers, presentation) {
|
|
1272
1464
|
const statusKind = {
|
|
1273
1465
|
ok: 'success',
|
|
@@ -1322,16 +1514,26 @@ async function doctorCommand(parsed, context) {
|
|
|
1322
1514
|
}
|
|
1323
1515
|
if (manifest) {
|
|
1324
1516
|
layers.push(await projectLayer(projectRoot, manifest));
|
|
1325
|
-
layers.push(await runtimeLayer(projectRoot, dockerAvailable, runner));
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1517
|
+
layers.push(await runtimeLayer(projectRoot, dockerAvailable, runner, manifest));
|
|
1518
|
+
if (manifest.project.workload.kind === 'power-apps-code-app') {
|
|
1519
|
+
if (manifest.project.workload.codeAppsPlugin) {
|
|
1520
|
+
layers.push(await codeAppsPluginLayer(projectRoot, manifest, runner));
|
|
1521
|
+
}
|
|
1522
|
+
}
|
|
1523
|
+
else {
|
|
1524
|
+
const workload = manifest.project.workload;
|
|
1525
|
+
const cloud = cloudOverride ?? workload.cloud;
|
|
1526
|
+
const cloudChecks = await cloudLayer(cloud, runner);
|
|
1527
|
+
const pattern = workload.kind === 'genai'
|
|
1528
|
+
? patterns.find((candidate) => candidate.id === workload.pattern)
|
|
1529
|
+
: undefined;
|
|
1530
|
+
if (pattern?.worker && cloud === 'azure') {
|
|
1531
|
+
cloudChecks.checks.push(binaryPresent('func')
|
|
1532
|
+
? { label: 'functions tooling', severity: 'ok', detail: 'Azure Functions Core Tools installed' }
|
|
1533
|
+
: { label: 'functions tooling', severity: 'warn', detail: 'Azure Functions Core Tools not found', remedy: 'npm install -g azure-functions-core-tools@4' });
|
|
1534
|
+
}
|
|
1535
|
+
layers.push(cloudChecks);
|
|
1333
1536
|
}
|
|
1334
|
-
layers.push(cloudChecks);
|
|
1335
1537
|
}
|
|
1336
1538
|
}
|
|
1337
1539
|
else if (cloudOverride) {
|
|
@@ -1348,7 +1550,24 @@ async function doctorCommand(parsed, context) {
|
|
|
1348
1550
|
}
|
|
1349
1551
|
return doctorExitCode(layers);
|
|
1350
1552
|
}
|
|
1351
|
-
function helperCommand(parsed, context, tool) {
|
|
1553
|
+
async function helperCommand(parsed, context, tool) {
|
|
1554
|
+
const projectRoot = await findProjectRoot(context.cwd);
|
|
1555
|
+
if (projectRoot) {
|
|
1556
|
+
const manifest = await loadManifest(projectRoot);
|
|
1557
|
+
if (manifest.project.workload.kind === 'power-apps-code-app') {
|
|
1558
|
+
if (parsed.command === 'dev') {
|
|
1559
|
+
context.presentation.commandIdentity('dev', 'Power Apps local development');
|
|
1560
|
+
context.presentation.section('Dependency prerequisite', ['Install the root lockfile before starting or after dependency changes.']);
|
|
1561
|
+
context.presentation.command('npm ci');
|
|
1562
|
+
context.presentation.section('Development server', []);
|
|
1563
|
+
context.presentation.command('npm run dev');
|
|
1564
|
+
return 0;
|
|
1565
|
+
}
|
|
1566
|
+
context.presentation.commandIdentity('infra', 'Power Apps hosting responsibility');
|
|
1567
|
+
context.presentation.status('info', 'Not applicable', 'Power Apps code apps are hosted by Power Platform; Liftoff-managed OpenTofu infrastructure is not generated.');
|
|
1568
|
+
return 0;
|
|
1569
|
+
}
|
|
1570
|
+
}
|
|
1352
1571
|
const command = parsed.command === 'dev' ? buildDevCommand(parsed) : buildInfraCommand(parsed);
|
|
1353
1572
|
context.presentation.commandIdentity(parsed.command ?? tool, `${tool} helper command`);
|
|
1354
1573
|
context.presentation.section(`${tool} helper command`, []);
|
|
@@ -1389,9 +1608,8 @@ async function optionsFromParsedArgs(parsed, cwd, includeProjectName) {
|
|
|
1389
1608
|
const configOptions = configPath ? await loadConfigOptions(configPath, cwd) : {};
|
|
1390
1609
|
const flagOptions = {
|
|
1391
1610
|
projectName: includeProjectName ? parsed.positional[0] ?? readStringFlag(parsed.flags, 'project') : readStringFlag(parsed.flags, 'project'),
|
|
1392
|
-
projectType:
|
|
1393
|
-
|
|
1394
|
-
: readBooleanFlag(parsed.flags, 'genai') ? 'genai' : 'standard',
|
|
1611
|
+
projectType: readStringFlag(parsed.flags, 'type'),
|
|
1612
|
+
genai: readBooleanFlag(parsed.flags, 'genai'),
|
|
1395
1613
|
apiStack: readStringFlag(parsed.flags, 'api'),
|
|
1396
1614
|
pattern: readStringFlag(parsed.flags, 'pattern'),
|
|
1397
1615
|
cloud: readStringFlag(parsed.flags, 'cloud'),
|
|
@@ -1401,6 +1619,7 @@ async function optionsFromParsedArgs(parsed, cwd, includeProjectName) {
|
|
|
1401
1619
|
specWorkflow: readStringFlag(parsed.flags, 'spec'),
|
|
1402
1620
|
agents: readListFlag(parsed.flags, 'agents'),
|
|
1403
1621
|
defaultAgent: readStringFlag(parsed.flags, 'default-agent'),
|
|
1622
|
+
codeAppsPlugin: readBooleanFlag(parsed.flags, 'code-apps-plugin'),
|
|
1404
1623
|
configPath,
|
|
1405
1624
|
yes: readBooleanFlag(parsed.flags, 'yes') ?? false,
|
|
1406
1625
|
force: readBooleanFlag(parsed.flags, 'force'),
|
|
@@ -1410,19 +1629,27 @@ async function optionsFromParsedArgs(parsed, cwd, includeProjectName) {
|
|
|
1410
1629
|
return mergeOptions(configOptions, flagOptions);
|
|
1411
1630
|
}
|
|
1412
1631
|
function hasMissingInitInputs(options) {
|
|
1413
|
-
const projectType = options.projectType ??
|
|
1414
|
-
|
|
1632
|
+
const projectType = options.projectType ??
|
|
1633
|
+
(options.genai === undefined ? undefined : options.genai ? 'genai' : 'standard') ??
|
|
1634
|
+
(options.pattern ? 'genai' : options.apiStack ? 'standard' : undefined);
|
|
1635
|
+
const missingTypeSpecific = projectType === 'genai'
|
|
1636
|
+
? !options.pattern
|
|
1637
|
+
: projectType === 'standard'
|
|
1638
|
+
? !options.apiStack
|
|
1639
|
+
: projectType === 'power-apps-code-app'
|
|
1640
|
+
? options.codeAppsPlugin === undefined
|
|
1641
|
+
: true;
|
|
1415
1642
|
const missingDefaultAgent = options.specWorkflow === 'spec-kit' &&
|
|
1416
1643
|
(options.agents?.length ?? 0) > 1 &&
|
|
1417
1644
|
!options.defaultAgent;
|
|
1418
1645
|
return !options.projectName ||
|
|
1419
1646
|
missingTypeSpecific ||
|
|
1420
|
-
!options.cloud ||
|
|
1421
|
-
options.includeFrontend === undefined ||
|
|
1647
|
+
projectType !== 'power-apps-code-app' && !options.cloud ||
|
|
1648
|
+
projectType !== 'power-apps-code-app' && options.includeFrontend === undefined ||
|
|
1422
1649
|
!options.specWorkflow ||
|
|
1423
1650
|
!options.agents ||
|
|
1424
1651
|
missingDefaultAgent ||
|
|
1425
|
-
!options.environments;
|
|
1652
|
+
projectType !== 'power-apps-code-app' && !options.environments;
|
|
1426
1653
|
}
|
|
1427
1654
|
function renderGeneralHelp(presentation) {
|
|
1428
1655
|
const help = getGeneralHelp(liftoffVersion);
|