@msn-control/liftoff 0.4.0 → 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 -224
- 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.d.ts +33 -0
- package/dist/args.js +100 -38
- 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/cli.js +6 -1
- package/dist/cli.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.d.ts +4 -0
- package/dist/commands.js +669 -321
- 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/framework-adapters.d.ts +5 -2
- package/dist/framework-adapters.js +5 -2
- package/dist/framework-adapters.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 +80 -6
- package/dist/interactive.js +338 -142
- 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.d.ts +5 -0
- package/dist/planner.js +197 -53
- 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/process-runner.js +9 -6
- package/dist/process-runner.js.map +1 -1
- package/dist/project-dependencies.d.ts +7 -4
- package/dist/project-dependencies.js +16 -4
- package/dist/project-dependencies.js.map +1 -1
- package/dist/published-verifier.js +1 -1
- package/dist/published-verifier.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/terminal.d.ts +99 -3
- package/dist/terminal.js +482 -66
- package/dist/terminal.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/interactive.d.ts
CHANGED
|
@@ -1,8 +1,82 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { Readable } from 'node:stream';
|
|
2
2
|
import type { DependencyCommandPlan } from './project-dependencies.js';
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
3
|
+
import { type CommandRunner } from './process-runner.js';
|
|
4
|
+
import { PresentationSession } from './terminal.js';
|
|
5
|
+
import type { CodingAgentId, ProjectOptions, ProjectPlan } from './types.js';
|
|
6
|
+
interface AgentCheckboxChoice {
|
|
7
|
+
name: string;
|
|
8
|
+
value: CodingAgentId;
|
|
9
|
+
checked: boolean;
|
|
10
|
+
}
|
|
11
|
+
export type AgentCheckboxPrompt = (config: {
|
|
12
|
+
message: string;
|
|
13
|
+
choices: AgentCheckboxChoice[];
|
|
14
|
+
required: boolean;
|
|
15
|
+
validate: (selected: readonly CodingAgentId[]) => true | string;
|
|
16
|
+
}, context: {
|
|
17
|
+
input: Readable;
|
|
18
|
+
output: NodeJS.WritableStream;
|
|
19
|
+
}) => Promise<CodingAgentId[]>;
|
|
20
|
+
export interface InteractiveDependencies {
|
|
21
|
+
input?: Readable;
|
|
22
|
+
output?: NodeJS.WritableStream;
|
|
23
|
+
presentation?: PresentationSession;
|
|
24
|
+
cwd?: string;
|
|
25
|
+
configuredRoot?: string;
|
|
26
|
+
runner?: CommandRunner;
|
|
27
|
+
checkboxPrompt?: AgentCheckboxPrompt;
|
|
28
|
+
}
|
|
29
|
+
export interface ToolInstallationPrompt {
|
|
30
|
+
label: string;
|
|
31
|
+
severity: string;
|
|
32
|
+
purpose: string;
|
|
33
|
+
requirement: string;
|
|
34
|
+
observed: string;
|
|
35
|
+
command?: string;
|
|
36
|
+
remedy?: string;
|
|
37
|
+
}
|
|
38
|
+
export declare class InteractiveCancelledError extends Error {
|
|
39
|
+
constructor(message?: string);
|
|
40
|
+
}
|
|
41
|
+
export declare class InteractivePrompter {
|
|
42
|
+
private readonly input;
|
|
43
|
+
private readonly output;
|
|
44
|
+
private readonly presentation;
|
|
45
|
+
private readonly cwd;
|
|
46
|
+
private readonly configuredRoot?;
|
|
47
|
+
private readonly runner?;
|
|
48
|
+
private readonly checkboxPrompt?;
|
|
49
|
+
private rl?;
|
|
50
|
+
private lines?;
|
|
51
|
+
private closed;
|
|
52
|
+
constructor(dependencies?: InteractiveDependencies);
|
|
53
|
+
close(): void;
|
|
54
|
+
promptForInitOptions(initial: ProjectOptions): Promise<ProjectOptions>;
|
|
55
|
+
confirmPlan(plan: ProjectPlan, yes?: boolean, actionLabel?: string): Promise<boolean>;
|
|
56
|
+
confirmToolInstallation(prompt: ToolInstallationPrompt | string): Promise<boolean>;
|
|
57
|
+
confirmFileReplacements(paths: readonly string[]): Promise<boolean>;
|
|
58
|
+
confirmDependencyInstallation(commands: DependencyCommandPlan[]): Promise<boolean>;
|
|
59
|
+
private askRequired;
|
|
60
|
+
private choose;
|
|
61
|
+
private confirm;
|
|
62
|
+
private promptForRegion;
|
|
63
|
+
private chooseRegion;
|
|
64
|
+
private askEnvironments;
|
|
65
|
+
private askAgents;
|
|
66
|
+
private askAgentsWithLines;
|
|
67
|
+
private askAgentsWithCheckbox;
|
|
68
|
+
private discoverAgents;
|
|
69
|
+
private pathExists;
|
|
70
|
+
private agentChoiceLabel;
|
|
71
|
+
private isRealTty;
|
|
72
|
+
private question;
|
|
73
|
+
private lineIterator;
|
|
74
|
+
private releaseLineInput;
|
|
75
|
+
}
|
|
76
|
+
export declare function promptForInitOptions(initial: ProjectOptions, dependencies?: InteractiveDependencies): Promise<ProjectOptions>;
|
|
77
|
+
export declare function confirmPlan(plan: ProjectPlan, yes?: boolean, dependencies?: InteractiveDependencies, actionLabel?: string): Promise<boolean>;
|
|
78
|
+
export declare function confirmToolInstallation(prompt: ToolInstallationPrompt | string, dependencies?: InteractiveDependencies): Promise<boolean>;
|
|
79
|
+
export declare function confirmFileReplacements(paths: readonly string[], dependencies?: InteractiveDependencies): Promise<boolean>;
|
|
80
|
+
export declare function confirmDependencyInstallation(commands: DependencyCommandPlan[], dependencies?: InteractiveDependencies): Promise<boolean>;
|
|
8
81
|
export declare function resolveCatalogInput(options: ProjectOptions): ProjectOptions;
|
|
82
|
+
export {};
|
package/dist/interactive.js
CHANGED
|
@@ -1,52 +1,116 @@
|
|
|
1
|
+
import { access } from 'node:fs/promises';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { stdin as processInput, stdout as processOutput } from 'node:process';
|
|
1
4
|
import { createInterface } from 'node:readline/promises';
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import { formatProjectPlan } from './planner.js';
|
|
5
|
+
import { apiStacks, codingAgents, environments, getApiStack, getCodingAgent, getFrameworkDefinition, getPattern, getProjectType, getProvider, getSpecWorkflow, patterns, projectTypes, providers, resolveRegion, specWorkflows } from './catalogs.js';
|
|
6
|
+
import { projectPlanEntries } from './planner.js';
|
|
5
7
|
import { formatCommand } from './process-runner.js';
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
8
|
+
import { PresentationSession } from './terminal.js';
|
|
9
|
+
export class InteractiveCancelledError extends Error {
|
|
10
|
+
constructor(message = 'Interactive initialization was cancelled.') {
|
|
11
|
+
super(message);
|
|
12
|
+
this.name = 'InteractiveCancelledError';
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
function errorCode(error) {
|
|
16
|
+
return typeof error === 'object' && error !== null && 'code' in error &&
|
|
17
|
+
typeof error.code === 'string'
|
|
18
|
+
? error.code
|
|
19
|
+
: undefined;
|
|
20
|
+
}
|
|
21
|
+
function isPromptCancellation(error) {
|
|
22
|
+
return error instanceof Error && (error.name === 'ExitPromptError' ||
|
|
23
|
+
error.name === 'AbortPromptError' ||
|
|
24
|
+
error.message.includes('User force closed the prompt'));
|
|
25
|
+
}
|
|
26
|
+
export class InteractivePrompter {
|
|
27
|
+
input;
|
|
28
|
+
output;
|
|
29
|
+
presentation;
|
|
30
|
+
cwd;
|
|
31
|
+
configuredRoot;
|
|
32
|
+
runner;
|
|
33
|
+
checkboxPrompt;
|
|
34
|
+
rl;
|
|
35
|
+
lines;
|
|
36
|
+
closed = false;
|
|
37
|
+
constructor(dependencies = {}) {
|
|
38
|
+
this.input = dependencies.input ?? processInput;
|
|
39
|
+
this.output = dependencies.output ?? processOutput;
|
|
40
|
+
this.presentation = dependencies.presentation ?? new PresentationSession({
|
|
41
|
+
stdout: this.output,
|
|
42
|
+
stderr: this.output
|
|
43
|
+
});
|
|
44
|
+
this.cwd = dependencies.cwd ?? process.cwd();
|
|
45
|
+
this.configuredRoot = dependencies.configuredRoot;
|
|
46
|
+
this.runner = dependencies.runner;
|
|
47
|
+
this.checkboxPrompt = dependencies.checkboxPrompt;
|
|
48
|
+
}
|
|
49
|
+
close() {
|
|
50
|
+
this.closed = true;
|
|
51
|
+
this.releaseLineInput();
|
|
52
|
+
}
|
|
53
|
+
async promptForInitOptions(initial) {
|
|
54
|
+
const projectName = initial.projectName ?? await this.askRequired('Project name');
|
|
10
55
|
const inferredProjectType = initial.projectType ?? (initial.pattern ? 'genai' : initial.apiStack ? 'standard' : undefined);
|
|
11
|
-
const projectType = inferredProjectType ??
|
|
56
|
+
const projectType = inferredProjectType ??
|
|
57
|
+
await this.choose('Select workload', projectTypes.map((workload) => ({
|
|
58
|
+
value: workload.id,
|
|
59
|
+
label: workload.label,
|
|
60
|
+
disabled: false
|
|
61
|
+
})), 'genai');
|
|
12
62
|
const pattern = projectType === 'genai'
|
|
13
|
-
? initial.pattern ?? await choose(
|
|
63
|
+
? initial.pattern ?? await this.choose('Select GenAI pattern', patterns.map((pattern) => ({
|
|
14
64
|
value: pattern.id,
|
|
15
65
|
label: `${pattern.label} (${pattern.scaffoldStatus})`,
|
|
16
66
|
disabled: false
|
|
17
67
|
})))
|
|
18
68
|
: initial.pattern;
|
|
19
69
|
const apiStack = projectType === 'standard'
|
|
20
|
-
? initial.apiStack ?? await choose(
|
|
70
|
+
? initial.apiStack ?? await this.choose('Select API stack', apiStacks.map((stack) => ({
|
|
21
71
|
value: stack.id,
|
|
22
72
|
label: `${stack.label} (${stack.databaseTooling})`,
|
|
23
73
|
disabled: false
|
|
24
74
|
})), 'python-fastapi')
|
|
25
|
-
:
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
75
|
+
: projectType === 'genai'
|
|
76
|
+
? initial.apiStack ?? 'python-fastapi'
|
|
77
|
+
: initial.apiStack;
|
|
78
|
+
const cloud = projectType === 'power-apps-code-app'
|
|
79
|
+
? initial.cloud
|
|
80
|
+
: initial.cloud ?? await this.choose('Target cloud', providers.map((provider) => ({
|
|
81
|
+
value: provider.id,
|
|
82
|
+
label: `${provider.label}${provider.status === 'planned' ? ' - planned' : ''}`,
|
|
83
|
+
disabled: provider.status === 'planned'
|
|
84
|
+
})));
|
|
85
|
+
const region = projectType === 'power-apps-code-app'
|
|
86
|
+
? initial.region
|
|
87
|
+
: initial.region ?? await this.promptForRegion(cloud);
|
|
88
|
+
const includeFrontend = projectType === 'power-apps-code-app'
|
|
89
|
+
? initial.includeFrontend
|
|
90
|
+
: initial.includeFrontend ?? await this.confirm('Include frontend? (Vue 3 + Tailwind)', false);
|
|
91
|
+
const specWorkflow = initial.specWorkflow ?? await this.choose('Select spec-driven workflow', specWorkflows.map((workflow) => ({
|
|
34
92
|
value: workflow.id,
|
|
35
|
-
label:
|
|
93
|
+
label: workflow.label,
|
|
36
94
|
disabled: false
|
|
37
95
|
})), 'openspec');
|
|
38
|
-
const selectedAgents = initial.agents ?? await askAgents(
|
|
96
|
+
const selectedAgents = initial.agents ?? await this.askAgents(specWorkflow);
|
|
39
97
|
const normalizedAgents = selectedAgents
|
|
40
98
|
.map((agent) => getCodingAgent(agent)?.id)
|
|
41
99
|
.filter((agent) => agent !== undefined);
|
|
42
100
|
const defaultAgent = specWorkflow === 'spec-kit' && normalizedAgents.length > 1
|
|
43
|
-
? initial.defaultAgent ?? await choose(
|
|
101
|
+
? initial.defaultAgent ?? await this.choose('Select the default Spec Kit agent', codingAgents
|
|
44
102
|
.filter((agent) => normalizedAgents.includes(agent.id))
|
|
45
103
|
.map((agent) => ({ value: agent.id, label: agent.label, disabled: false })), normalizedAgents[0])
|
|
46
104
|
: specWorkflow === 'spec-kit'
|
|
47
105
|
? normalizedAgents[0]
|
|
48
106
|
: undefined;
|
|
49
|
-
const
|
|
107
|
+
const codeAppsPlugin = projectType === 'power-apps-code-app'
|
|
108
|
+
? initial.codeAppsPlugin ??
|
|
109
|
+
await this.confirm('Include Microsoft Code Apps preview plugin guidance?', false)
|
|
110
|
+
: initial.codeAppsPlugin;
|
|
111
|
+
const selectedEnvironments = projectType === 'power-apps-code-app'
|
|
112
|
+
? initial.environments
|
|
113
|
+
: initial.environments ?? await this.askEnvironments();
|
|
50
114
|
return {
|
|
51
115
|
...initial,
|
|
52
116
|
projectName,
|
|
@@ -59,158 +123,290 @@ export async function promptForInitOptions(initial) {
|
|
|
59
123
|
specWorkflow,
|
|
60
124
|
agents: normalizedAgents,
|
|
61
125
|
...(defaultAgent ? { defaultAgent } : {}),
|
|
62
|
-
environments: selectedEnvironments
|
|
126
|
+
...(selectedEnvironments ? { environments: selectedEnvironments } : {}),
|
|
127
|
+
...(codeAppsPlugin !== undefined ? { codeAppsPlugin } : {})
|
|
63
128
|
};
|
|
64
129
|
}
|
|
65
|
-
|
|
66
|
-
|
|
130
|
+
async confirmPlan(plan, yes, actionLabel = 'Initialize project?') {
|
|
131
|
+
this.presentation.definitions('Resolved project plan', projectPlanEntries(plan));
|
|
132
|
+
return yes ? true : this.confirm(actionLabel, true);
|
|
67
133
|
}
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
134
|
+
async confirmToolInstallation(prompt) {
|
|
135
|
+
if (typeof prompt === 'string') {
|
|
136
|
+
this.presentation.section('Workstation tool requiring action', prompt.split(/\r?\n/));
|
|
137
|
+
}
|
|
138
|
+
else {
|
|
139
|
+
this.presentation.definitions('Workstation tool requiring action', [
|
|
140
|
+
{ label: 'Tool', value: `${prompt.label} [${prompt.severity}]` },
|
|
141
|
+
{ label: 'Purpose', value: prompt.purpose },
|
|
142
|
+
{ label: 'Requirement', value: prompt.requirement },
|
|
143
|
+
{ label: 'Observed', value: prompt.observed }
|
|
144
|
+
]);
|
|
145
|
+
if (prompt.command) {
|
|
146
|
+
this.presentation.command(prompt.command);
|
|
147
|
+
}
|
|
148
|
+
if (prompt.remedy) {
|
|
149
|
+
this.presentation.remedy(prompt.remedy);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
return this.confirm('Run this allowlisted installation command?', false);
|
|
72
153
|
}
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
return await confirm(rl, 'Initialize project?', true);
|
|
154
|
+
async confirmFileReplacements(paths) {
|
|
155
|
+
this.presentation.bullets('Existing regular files with different content', paths);
|
|
156
|
+
return this.confirm('Replace every listed file?', false);
|
|
77
157
|
}
|
|
78
|
-
|
|
79
|
-
|
|
158
|
+
async confirmDependencyInstallation(commands) {
|
|
159
|
+
this.presentation.table('Project dependency commands', ['Purpose', 'Working directory', 'Command'], commands.map((command) => [
|
|
160
|
+
command.label,
|
|
161
|
+
command.cwd,
|
|
162
|
+
formatCommand(command.command)
|
|
163
|
+
]));
|
|
164
|
+
return this.confirm('Install project-local dependencies now?', false);
|
|
80
165
|
}
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
166
|
+
async askRequired(label) {
|
|
167
|
+
while (true) {
|
|
168
|
+
const answer = (await this.question(label)).trim();
|
|
169
|
+
if (answer) {
|
|
170
|
+
return answer;
|
|
171
|
+
}
|
|
172
|
+
this.presentation.warning(`${label} is required.`);
|
|
87
173
|
}
|
|
88
174
|
}
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
175
|
+
async choose(label, options, defaultValue) {
|
|
176
|
+
while (true) {
|
|
177
|
+
this.presentation.choices(label, options.map((option) => ({
|
|
178
|
+
label: option.label,
|
|
179
|
+
value: option.value,
|
|
180
|
+
disabled: option.disabled,
|
|
181
|
+
default: option.value === defaultValue
|
|
182
|
+
})));
|
|
183
|
+
const answer = (await this.question('Select option', defaultValue)).trim();
|
|
184
|
+
if (!answer && defaultValue) {
|
|
185
|
+
return defaultValue;
|
|
186
|
+
}
|
|
187
|
+
const selectedIndex = Number(answer) - 1;
|
|
188
|
+
const selected = options[selectedIndex] ?? options.find((option) => option.value === answer);
|
|
189
|
+
if (!selected) {
|
|
190
|
+
this.presentation.warning('Please choose a valid option.');
|
|
191
|
+
continue;
|
|
192
|
+
}
|
|
193
|
+
if (selected.disabled) {
|
|
194
|
+
this.presentation.warning(`${selected.label} is not available in V1.`);
|
|
195
|
+
continue;
|
|
196
|
+
}
|
|
197
|
+
return selected.value;
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
async confirm(label, defaultValue) {
|
|
201
|
+
const suffix = defaultValue ? 'Y/n' : 'y/N';
|
|
202
|
+
const answer = (await this.question(label, suffix)).trim().toLowerCase();
|
|
203
|
+
if (!answer) {
|
|
98
204
|
return defaultValue;
|
|
99
205
|
}
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
206
|
+
return answer === 'y' || answer === 'yes';
|
|
207
|
+
}
|
|
208
|
+
async promptForRegion(cloud) {
|
|
209
|
+
const provider = getProvider(cloud);
|
|
210
|
+
if (!provider || provider.id !== 'azure') {
|
|
211
|
+
return '';
|
|
105
212
|
}
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
213
|
+
while (true) {
|
|
214
|
+
const answer = (await this.question('Azure region', 'East US / eastus')).trim();
|
|
215
|
+
const resolution = resolveRegion(provider.id, answer);
|
|
216
|
+
if (resolution.status === 'resolved') {
|
|
217
|
+
return resolution.region.slug;
|
|
218
|
+
}
|
|
219
|
+
if (resolution.status === 'ambiguous') {
|
|
220
|
+
const selected = await this.chooseRegion(resolution.matches);
|
|
221
|
+
return selected.slug;
|
|
222
|
+
}
|
|
223
|
+
this.presentation.warning(`Unknown Azure region: ${resolution.input}. Try a region name such as eastus or korea.`);
|
|
109
224
|
}
|
|
110
|
-
return selected.value;
|
|
111
225
|
}
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
226
|
+
async chooseRegion(matches) {
|
|
227
|
+
this.presentation.table('Matching Azure regions', ['Option', 'Region', 'Identifier'], matches.map((region, index) => [`${index + 1}`, region.displayName, region.slug]));
|
|
228
|
+
while (true) {
|
|
229
|
+
const answer = (await this.question('Select deployment region')).trim();
|
|
230
|
+
const selected = matches[Number(answer) - 1] ??
|
|
231
|
+
matches.find((region) => region.slug === answer);
|
|
232
|
+
if (selected) {
|
|
233
|
+
return selected;
|
|
234
|
+
}
|
|
235
|
+
this.presentation.warning('Please choose a valid deployment region.');
|
|
236
|
+
}
|
|
118
237
|
}
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
return '';
|
|
238
|
+
async askEnvironments() {
|
|
239
|
+
const answer = (await this.question('Environments', 'dev,test,prod')).trim();
|
|
240
|
+
if (!answer) {
|
|
241
|
+
return environments.map((environment) => environment.id);
|
|
242
|
+
}
|
|
243
|
+
return answer.split(',').map((value) => value.trim()).filter(Boolean);
|
|
125
244
|
}
|
|
126
|
-
|
|
127
|
-
const
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
return resolution.region.slug;
|
|
245
|
+
async askAgents(specWorkflow) {
|
|
246
|
+
const discovery = await this.discoverAgents(specWorkflow);
|
|
247
|
+
if (this.isRealTty()) {
|
|
248
|
+
return this.askAgentsWithCheckbox(discovery);
|
|
131
249
|
}
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
250
|
+
return this.askAgentsWithLines(discovery);
|
|
251
|
+
}
|
|
252
|
+
async askAgentsWithLines(discovery) {
|
|
253
|
+
while (true) {
|
|
254
|
+
this.presentation.choices('Select one or more AI coding agents', codingAgents.map((agent) => ({
|
|
255
|
+
label: this.agentChoiceLabel(agent.id, discovery),
|
|
256
|
+
value: agent.id,
|
|
257
|
+
default: discovery.defaults.includes(agent.id),
|
|
258
|
+
selected: discovery.defaults.includes(agent.id)
|
|
259
|
+
})));
|
|
260
|
+
const defaultSelection = discovery.defaults
|
|
261
|
+
.map((agentId) => `${codingAgents.findIndex((agent) => agent.id === agentId) + 1}`)
|
|
262
|
+
.join(',');
|
|
263
|
+
const answer = (await this.question('Select comma-separated options', defaultSelection)).trim() || defaultSelection;
|
|
264
|
+
const selected = answer.split(',').map((value) => value.trim()).filter(Boolean);
|
|
265
|
+
const resolved = selected.map((value) => {
|
|
266
|
+
const byIndex = codingAgents[Number(value) - 1];
|
|
267
|
+
return byIndex ?? getCodingAgent(value);
|
|
268
|
+
});
|
|
269
|
+
if (resolved.some((agent) => !agent)) {
|
|
270
|
+
this.presentation.warning('Please choose valid agent options.');
|
|
271
|
+
continue;
|
|
272
|
+
}
|
|
273
|
+
return codingAgents
|
|
274
|
+
.filter((agent) => resolved.some((selectedAgent) => selectedAgent?.id === agent.id))
|
|
275
|
+
.map((agent) => agent.id);
|
|
135
276
|
}
|
|
136
|
-
output.write(`Unknown Azure region: ${resolution.input}. Try a region name such as eastus or korea.\n`);
|
|
137
277
|
}
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
278
|
+
async askAgentsWithCheckbox(discovery) {
|
|
279
|
+
this.releaseLineInput();
|
|
280
|
+
try {
|
|
281
|
+
const checkbox = this.checkboxPrompt ?? (await import('@inquirer/prompts')).checkbox;
|
|
282
|
+
const selected = await checkbox({
|
|
283
|
+
message: 'Select one or more AI coding agents',
|
|
284
|
+
choices: codingAgents.map((agent) => ({
|
|
285
|
+
name: this.agentChoiceLabel(agent.id, discovery),
|
|
286
|
+
value: agent.id,
|
|
287
|
+
checked: discovery.defaults.includes(agent.id)
|
|
288
|
+
})),
|
|
289
|
+
required: true,
|
|
290
|
+
validate: (values) => values.length > 0 || 'Select at least one AI coding agent.'
|
|
291
|
+
}, {
|
|
292
|
+
input: this.input,
|
|
293
|
+
output: this.output
|
|
294
|
+
});
|
|
295
|
+
return codingAgents
|
|
296
|
+
.filter((agent) => selected.includes(agent.id))
|
|
297
|
+
.map((agent) => agent.id);
|
|
298
|
+
}
|
|
299
|
+
catch (error) {
|
|
300
|
+
if (isPromptCancellation(error)) {
|
|
301
|
+
throw new InteractiveCancelledError();
|
|
302
|
+
}
|
|
303
|
+
throw error;
|
|
149
304
|
}
|
|
150
305
|
}
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
306
|
+
async discoverAgents(specWorkflow) {
|
|
307
|
+
const configured = new Set();
|
|
308
|
+
const detected = new Set();
|
|
309
|
+
const framework = getFrameworkDefinition(specWorkflow);
|
|
310
|
+
await Promise.all(codingAgents.map(async (agent) => {
|
|
311
|
+
if (this.configuredRoot) {
|
|
312
|
+
const markerStates = await Promise.all(framework.agentMarkers[agent.id].map(async (pathParts) => this.pathExists(path.join(this.configuredRoot, ...pathParts))));
|
|
313
|
+
if (markerStates.some(Boolean)) {
|
|
314
|
+
configured.add(agent.id);
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
if (this.runner) {
|
|
318
|
+
const probe = await this.runner.run({ executable: agent.executable, args: ['--version'] }, { cwd: this.cwd, timeoutMs: 10_000 });
|
|
319
|
+
if (probe.status === 0 && !probe.timedOut) {
|
|
320
|
+
detected.add(agent.id);
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
}));
|
|
324
|
+
const defaults = codingAgents
|
|
325
|
+
.filter((agent) => configured.size > 0
|
|
326
|
+
? configured.has(agent.id)
|
|
327
|
+
: detected.size > 0
|
|
328
|
+
? detected.has(agent.id)
|
|
329
|
+
: agent.id === 'github-copilot')
|
|
330
|
+
.map((agent) => agent.id);
|
|
331
|
+
return { configured, detected, defaults };
|
|
156
332
|
}
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
const resolved = selected.map((value) => {
|
|
168
|
-
const byIndex = codingAgents[Number(value) - 1];
|
|
169
|
-
return byIndex ?? getCodingAgent(value);
|
|
170
|
-
});
|
|
171
|
-
if (resolved.some((agent) => !agent)) {
|
|
172
|
-
output.write('Please choose valid agent options.\n');
|
|
173
|
-
continue;
|
|
333
|
+
async pathExists(file) {
|
|
334
|
+
try {
|
|
335
|
+
await access(file);
|
|
336
|
+
return true;
|
|
337
|
+
}
|
|
338
|
+
catch (error) {
|
|
339
|
+
if (errorCode(error) === 'ENOENT' || errorCode(error) === 'ENOTDIR') {
|
|
340
|
+
return false;
|
|
341
|
+
}
|
|
342
|
+
throw error;
|
|
174
343
|
}
|
|
175
|
-
return codingAgents
|
|
176
|
-
.filter((agent) => resolved.some((selectedAgent) => selectedAgent?.id === agent.id))
|
|
177
|
-
.map((agent) => agent.id);
|
|
178
344
|
}
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
345
|
+
agentChoiceLabel(agentId, discovery) {
|
|
346
|
+
const agent = codingAgents.find((candidate) => candidate.id === agentId);
|
|
347
|
+
if (!agent) {
|
|
348
|
+
throw new Error(`Unknown coding agent: ${agentId}`);
|
|
349
|
+
}
|
|
350
|
+
const states = [
|
|
351
|
+
discovery.configured.has(agentId) ? 'configured' : undefined,
|
|
352
|
+
discovery.detected.has(agentId) ? 'detected' : undefined
|
|
353
|
+
].filter((state) => state !== undefined);
|
|
354
|
+
return `${agent.label} (${states.length > 0 ? states.join(', ') : 'not observable'})`;
|
|
185
355
|
}
|
|
186
|
-
|
|
187
|
-
|
|
356
|
+
isRealTty() {
|
|
357
|
+
const input = this.input;
|
|
358
|
+
const output = this.output;
|
|
359
|
+
return input.isTTY === true && typeof input.setRawMode === 'function' && output.isTTY === true;
|
|
188
360
|
}
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
361
|
+
async question(label, defaultValue) {
|
|
362
|
+
this.presentation.prompt(label, defaultValue);
|
|
363
|
+
const answer = await this.lineIterator().next();
|
|
364
|
+
if (answer.done) {
|
|
365
|
+
throw new InteractiveCancelledError(`Interactive input closed before answering: ${label}.`);
|
|
366
|
+
}
|
|
367
|
+
return answer.value;
|
|
195
368
|
}
|
|
196
|
-
|
|
197
|
-
|
|
369
|
+
lineIterator() {
|
|
370
|
+
if (this.closed) {
|
|
371
|
+
throw new InteractiveCancelledError();
|
|
372
|
+
}
|
|
373
|
+
if (!this.rl || !this.lines) {
|
|
374
|
+
this.rl = createInterface({ input: this.input, terminal: false });
|
|
375
|
+
this.rl.on('SIGINT', () => this.rl?.close());
|
|
376
|
+
this.lines = this.rl[Symbol.asyncIterator]();
|
|
377
|
+
}
|
|
378
|
+
return this.lines;
|
|
379
|
+
}
|
|
380
|
+
releaseLineInput() {
|
|
381
|
+
this.rl?.close();
|
|
382
|
+
this.rl = undefined;
|
|
383
|
+
this.lines = undefined;
|
|
198
384
|
}
|
|
199
385
|
}
|
|
200
|
-
|
|
201
|
-
const
|
|
386
|
+
async function withPrompter(dependencies, callback) {
|
|
387
|
+
const prompter = new InteractivePrompter(dependencies);
|
|
202
388
|
try {
|
|
203
|
-
|
|
204
|
-
for (const command of commands) {
|
|
205
|
-
output.write(`- ${command.cwd}: ${formatCommand(command.command)}\n`);
|
|
206
|
-
}
|
|
207
|
-
output.write('\n');
|
|
208
|
-
return await confirm(rl, 'Install project-local dependencies now?', false);
|
|
389
|
+
return await callback(prompter);
|
|
209
390
|
}
|
|
210
391
|
finally {
|
|
211
|
-
|
|
392
|
+
prompter.close();
|
|
212
393
|
}
|
|
213
394
|
}
|
|
395
|
+
export async function promptForInitOptions(initial, dependencies) {
|
|
396
|
+
return withPrompter(dependencies, (prompter) => prompter.promptForInitOptions(initial));
|
|
397
|
+
}
|
|
398
|
+
export async function confirmPlan(plan, yes, dependencies, actionLabel) {
|
|
399
|
+
return withPrompter(dependencies, (prompter) => prompter.confirmPlan(plan, yes, actionLabel));
|
|
400
|
+
}
|
|
401
|
+
export async function confirmToolInstallation(prompt, dependencies) {
|
|
402
|
+
return withPrompter(dependencies, (prompter) => prompter.confirmToolInstallation(prompt));
|
|
403
|
+
}
|
|
404
|
+
export async function confirmFileReplacements(paths, dependencies) {
|
|
405
|
+
return withPrompter(dependencies, (prompter) => prompter.confirmFileReplacements(paths));
|
|
406
|
+
}
|
|
407
|
+
export async function confirmDependencyInstallation(commands, dependencies) {
|
|
408
|
+
return withPrompter(dependencies, (prompter) => prompter.confirmDependencyInstallation(commands));
|
|
409
|
+
}
|
|
214
410
|
export function resolveCatalogInput(options) {
|
|
215
411
|
return {
|
|
216
412
|
...options,
|