wp-typia 0.16.3 → 0.16.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +2 -2
- package/src/runtime-bridge.ts +249 -37
- package/src/ui/add-flow.tsx +24 -7
- package/src/ui/alternate-buffer-lifecycle.ts +98 -12
- package/src/ui/create-flow.tsx +24 -6
- package/src/ui/first-party-form.tsx +260 -0
- package/src/ui/migrate-flow.tsx +24 -6
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "wp-typia",
|
|
3
|
-
"version": "0.16.
|
|
3
|
+
"version": "0.16.6",
|
|
4
4
|
"description": "Canonical CLI package for wp-typia scaffolding and project workflows",
|
|
5
5
|
"packageManager": "bun@1.3.11",
|
|
6
6
|
"type": "module",
|
|
@@ -65,7 +65,7 @@
|
|
|
65
65
|
"@bunli/tui": "0.6.0",
|
|
66
66
|
"@bunli/utils": "0.6.0",
|
|
67
67
|
"@wp-typia/api-client": "^0.4.2",
|
|
68
|
-
"@wp-typia/project-tools": "0.16.
|
|
68
|
+
"@wp-typia/project-tools": "0.16.5",
|
|
69
69
|
"better-result": "^2.7.0",
|
|
70
70
|
"react": "^19.2.5",
|
|
71
71
|
"react-dom": "^19.2.5",
|
package/src/runtime-bridge.ts
CHANGED
|
@@ -12,20 +12,26 @@ import {
|
|
|
12
12
|
import { formatRunScript } from "@wp-typia/project-tools/package-managers";
|
|
13
13
|
import { tryResolveWorkspaceProject } from "@wp-typia/project-tools/workspace-project";
|
|
14
14
|
import type { ReadlinePrompt } from "@wp-typia/project-tools/cli-prompt";
|
|
15
|
+
import type { AlternateBufferCompletionPayload } from "./ui/alternate-buffer-lifecycle";
|
|
15
16
|
|
|
16
17
|
type CreateExecutionInput = {
|
|
17
18
|
projectDir: string;
|
|
18
19
|
cwd: string;
|
|
20
|
+
emitOutput?: boolean;
|
|
19
21
|
flags: Record<string, unknown>;
|
|
20
22
|
interactive?: boolean;
|
|
23
|
+
printLine?: PrintLine;
|
|
21
24
|
prompt?: ReadlinePrompt;
|
|
25
|
+
warnLine?: PrintLine;
|
|
22
26
|
};
|
|
23
27
|
|
|
24
28
|
type AddExecutionInput = {
|
|
25
29
|
cwd: string;
|
|
30
|
+
emitOutput?: boolean;
|
|
26
31
|
flags: Record<string, unknown>;
|
|
27
32
|
kind?: string;
|
|
28
33
|
name?: string;
|
|
34
|
+
printLine?: PrintLine;
|
|
29
35
|
};
|
|
30
36
|
|
|
31
37
|
type TemplatesExecutionInput = {
|
|
@@ -71,6 +77,151 @@ function printBlock(lines: string[], printLine: PrintLine): void {
|
|
|
71
77
|
}
|
|
72
78
|
}
|
|
73
79
|
|
|
80
|
+
export function printCompletionPayload(
|
|
81
|
+
payload: AlternateBufferCompletionPayload,
|
|
82
|
+
options: {
|
|
83
|
+
printLine?: PrintLine;
|
|
84
|
+
warnLine?: PrintLine;
|
|
85
|
+
} = {},
|
|
86
|
+
): void {
|
|
87
|
+
const printLine = options.printLine ?? (console.log as PrintLine);
|
|
88
|
+
const warnLine = options.warnLine ?? printLine;
|
|
89
|
+
|
|
90
|
+
for (const line of payload.preambleLines ?? []) {
|
|
91
|
+
printLine(line);
|
|
92
|
+
}
|
|
93
|
+
for (const warning of payload.warningLines ?? []) {
|
|
94
|
+
warnLine(`⚠️ ${warning}`);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
const hasDetails =
|
|
98
|
+
(payload.summaryLines?.length ?? 0) > 0 ||
|
|
99
|
+
(payload.nextSteps?.length ?? 0) > 0 ||
|
|
100
|
+
(payload.optionalLines?.length ?? 0) > 0 ||
|
|
101
|
+
Boolean(payload.optionalNote);
|
|
102
|
+
const hasLeadingContext =
|
|
103
|
+
(payload.preambleLines?.length ?? 0) > 0 ||
|
|
104
|
+
(payload.warningLines?.length ?? 0) > 0;
|
|
105
|
+
|
|
106
|
+
printLine(hasLeadingContext && hasDetails ? `\n${payload.title}` : payload.title);
|
|
107
|
+
for (const line of payload.summaryLines ?? []) {
|
|
108
|
+
printLine(line);
|
|
109
|
+
}
|
|
110
|
+
if ((payload.nextSteps?.length ?? 0) > 0) {
|
|
111
|
+
printLine("Next steps:");
|
|
112
|
+
for (const step of payload.nextSteps ?? []) {
|
|
113
|
+
printLine(` ${step}`);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
if ((payload.optionalLines?.length ?? 0) > 0) {
|
|
117
|
+
printLine(`\n${payload.optionalTitle ?? "Optional:"}`);
|
|
118
|
+
for (const step of payload.optionalLines ?? []) {
|
|
119
|
+
printLine(` ${step}`);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
if (payload.optionalNote) {
|
|
123
|
+
printLine(`Note: ${payload.optionalNote}`);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
export function buildCreateCompletionPayload(flow: {
|
|
128
|
+
nextSteps: string[];
|
|
129
|
+
optionalOnboarding: {
|
|
130
|
+
note: string;
|
|
131
|
+
steps: string[];
|
|
132
|
+
};
|
|
133
|
+
projectDir: string;
|
|
134
|
+
result: {
|
|
135
|
+
selectedVariant?: string | null;
|
|
136
|
+
variables: {
|
|
137
|
+
title: string;
|
|
138
|
+
};
|
|
139
|
+
warnings: string[];
|
|
140
|
+
};
|
|
141
|
+
}): AlternateBufferCompletionPayload {
|
|
142
|
+
return {
|
|
143
|
+
nextSteps: flow.nextSteps,
|
|
144
|
+
optionalLines:
|
|
145
|
+
flow.optionalOnboarding.steps.length > 0 ? flow.optionalOnboarding.steps : undefined,
|
|
146
|
+
optionalNote:
|
|
147
|
+
flow.optionalOnboarding.steps.length > 0 ? flow.optionalOnboarding.note : undefined,
|
|
148
|
+
optionalTitle:
|
|
149
|
+
flow.optionalOnboarding.steps.length > 0 ? "Optional before first commit:" : undefined,
|
|
150
|
+
preambleLines: flow.result.selectedVariant
|
|
151
|
+
? [`Template variant: ${flow.result.selectedVariant}`]
|
|
152
|
+
: undefined,
|
|
153
|
+
summaryLines: [`Project directory: ${flow.projectDir}`],
|
|
154
|
+
title: `✅ Created ${flow.result.variables.title} in ${flow.projectDir}`,
|
|
155
|
+
warningLines: flow.result.warnings,
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
export function buildMigrationCompletionPayload(options: {
|
|
160
|
+
command: string;
|
|
161
|
+
lines: string[];
|
|
162
|
+
}): AlternateBufferCompletionPayload {
|
|
163
|
+
const summaryLines = options.lines.filter((line) => line.trim().length > 0);
|
|
164
|
+
|
|
165
|
+
return {
|
|
166
|
+
summaryLines,
|
|
167
|
+
title: `✅ Completed wp-typia migrate ${options.command}`,
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
function buildAddCompletionPayload(options: {
|
|
172
|
+
kind: "binding-source" | "block" | "hooked-block" | "pattern" | "variation";
|
|
173
|
+
projectDir: string;
|
|
174
|
+
values: Record<string, string>;
|
|
175
|
+
}): AlternateBufferCompletionPayload {
|
|
176
|
+
switch (options.kind) {
|
|
177
|
+
case "variation":
|
|
178
|
+
return {
|
|
179
|
+
summaryLines: [
|
|
180
|
+
`Variation: ${options.values.variationSlug}`,
|
|
181
|
+
`Target block: ${options.values.blockSlug}`,
|
|
182
|
+
`Project directory: ${options.projectDir}`,
|
|
183
|
+
],
|
|
184
|
+
title: "✅ Added workspace variation",
|
|
185
|
+
};
|
|
186
|
+
case "pattern":
|
|
187
|
+
return {
|
|
188
|
+
summaryLines: [
|
|
189
|
+
`Pattern: ${options.values.patternSlug}`,
|
|
190
|
+
`Project directory: ${options.projectDir}`,
|
|
191
|
+
],
|
|
192
|
+
title: "✅ Added workspace pattern",
|
|
193
|
+
};
|
|
194
|
+
case "binding-source":
|
|
195
|
+
return {
|
|
196
|
+
summaryLines: [
|
|
197
|
+
`Binding source: ${options.values.bindingSourceSlug}`,
|
|
198
|
+
`Project directory: ${options.projectDir}`,
|
|
199
|
+
],
|
|
200
|
+
title: "✅ Added binding source",
|
|
201
|
+
};
|
|
202
|
+
case "hooked-block":
|
|
203
|
+
return {
|
|
204
|
+
summaryLines: [
|
|
205
|
+
`Block: ${options.values.blockSlug}`,
|
|
206
|
+
`Anchor: ${options.values.anchorBlockName}`,
|
|
207
|
+
`Position: ${options.values.position}`,
|
|
208
|
+
`Project directory: ${options.projectDir}`,
|
|
209
|
+
],
|
|
210
|
+
title: "✅ Added blockHooks metadata",
|
|
211
|
+
};
|
|
212
|
+
case "block":
|
|
213
|
+
default:
|
|
214
|
+
return {
|
|
215
|
+
summaryLines: [
|
|
216
|
+
`Blocks: ${options.values.blockSlugs}`,
|
|
217
|
+
`Template family: ${options.values.templateId}`,
|
|
218
|
+
`Project directory: ${options.projectDir}`,
|
|
219
|
+
],
|
|
220
|
+
title: "✅ Added workspace block",
|
|
221
|
+
};
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
|
|
74
225
|
function readOptionalStringFlag(
|
|
75
226
|
flags: Record<string, unknown>,
|
|
76
227
|
name: string,
|
|
@@ -261,10 +412,13 @@ const BOOLEAN_PROMPT_OPTIONS = [
|
|
|
261
412
|
export async function executeCreateCommand({
|
|
262
413
|
projectDir,
|
|
263
414
|
cwd,
|
|
415
|
+
emitOutput = true,
|
|
264
416
|
flags,
|
|
265
417
|
interactive,
|
|
418
|
+
printLine = console.log as PrintLine,
|
|
266
419
|
prompt,
|
|
267
|
-
|
|
420
|
+
warnLine = console.warn as PrintLine,
|
|
421
|
+
}: CreateExecutionInput): Promise<AlternateBufferCompletionPayload> {
|
|
268
422
|
const [
|
|
269
423
|
{ createReadlinePrompt },
|
|
270
424
|
{ runScaffoldFlow },
|
|
@@ -333,25 +487,14 @@ export async function executeCreateCommand({
|
|
|
333
487
|
yes: Boolean(flags.yes),
|
|
334
488
|
});
|
|
335
489
|
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
console.log(`\n✅ Created ${flow.result.variables.title} in ${flow.projectDir}`);
|
|
344
|
-
console.log("Next steps:");
|
|
345
|
-
for (const step of flow.nextSteps) {
|
|
346
|
-
console.log(` ${step}`);
|
|
347
|
-
}
|
|
348
|
-
if (flow.optionalOnboarding.steps.length > 0) {
|
|
349
|
-
console.log("\nOptional before first commit:");
|
|
350
|
-
for (const step of flow.optionalOnboarding.steps) {
|
|
351
|
-
console.log(` ${step}`);
|
|
352
|
-
}
|
|
353
|
-
console.log(`Note: ${flow.optionalOnboarding.note}`);
|
|
490
|
+
const payload = buildCreateCompletionPayload(flow);
|
|
491
|
+
if (emitOutput) {
|
|
492
|
+
printCompletionPayload(payload, {
|
|
493
|
+
printLine,
|
|
494
|
+
warnLine,
|
|
495
|
+
});
|
|
354
496
|
}
|
|
497
|
+
return payload;
|
|
355
498
|
} finally {
|
|
356
499
|
if (activePrompt && activePrompt !== prompt) {
|
|
357
500
|
activePrompt.close();
|
|
@@ -361,13 +504,15 @@ export async function executeCreateCommand({
|
|
|
361
504
|
|
|
362
505
|
export async function executeAddCommand({
|
|
363
506
|
cwd,
|
|
507
|
+
emitOutput = true,
|
|
364
508
|
flags,
|
|
365
509
|
kind,
|
|
366
510
|
name,
|
|
367
|
-
|
|
511
|
+
printLine = console.log as PrintLine,
|
|
512
|
+
}: AddExecutionInput): Promise<AlternateBufferCompletionPayload | void> {
|
|
368
513
|
if (!kind) {
|
|
369
514
|
const { formatAddHelpText } = await loadCliAddRuntime();
|
|
370
|
-
|
|
515
|
+
printLine(formatAddHelpText());
|
|
371
516
|
return;
|
|
372
517
|
}
|
|
373
518
|
|
|
@@ -390,8 +535,18 @@ export async function executeAddCommand({
|
|
|
390
535
|
cwd,
|
|
391
536
|
variationName: name,
|
|
392
537
|
});
|
|
393
|
-
|
|
394
|
-
|
|
538
|
+
const payload = buildAddCompletionPayload({
|
|
539
|
+
kind: "variation",
|
|
540
|
+
projectDir: result.projectDir,
|
|
541
|
+
values: {
|
|
542
|
+
blockSlug: result.blockSlug,
|
|
543
|
+
variationSlug: result.variationSlug,
|
|
544
|
+
},
|
|
545
|
+
});
|
|
546
|
+
if (emitOutput) {
|
|
547
|
+
printCompletionPayload(payload, { printLine });
|
|
548
|
+
}
|
|
549
|
+
return payload;
|
|
395
550
|
}
|
|
396
551
|
|
|
397
552
|
if (kind === "pattern") {
|
|
@@ -405,8 +560,17 @@ export async function executeAddCommand({
|
|
|
405
560
|
cwd,
|
|
406
561
|
patternName: name,
|
|
407
562
|
});
|
|
408
|
-
|
|
409
|
-
|
|
563
|
+
const payload = buildAddCompletionPayload({
|
|
564
|
+
kind: "pattern",
|
|
565
|
+
projectDir: result.projectDir,
|
|
566
|
+
values: {
|
|
567
|
+
patternSlug: result.patternSlug,
|
|
568
|
+
},
|
|
569
|
+
});
|
|
570
|
+
if (emitOutput) {
|
|
571
|
+
printCompletionPayload(payload, { printLine });
|
|
572
|
+
}
|
|
573
|
+
return payload;
|
|
410
574
|
}
|
|
411
575
|
|
|
412
576
|
if (kind === "binding-source") {
|
|
@@ -420,8 +584,17 @@ export async function executeAddCommand({
|
|
|
420
584
|
bindingSourceName: name,
|
|
421
585
|
cwd,
|
|
422
586
|
});
|
|
423
|
-
|
|
424
|
-
|
|
587
|
+
const payload = buildAddCompletionPayload({
|
|
588
|
+
kind: "binding-source",
|
|
589
|
+
projectDir: result.projectDir,
|
|
590
|
+
values: {
|
|
591
|
+
bindingSourceSlug: result.bindingSourceSlug,
|
|
592
|
+
},
|
|
593
|
+
});
|
|
594
|
+
if (emitOutput) {
|
|
595
|
+
printCompletionPayload(payload, { printLine });
|
|
596
|
+
}
|
|
597
|
+
return payload;
|
|
425
598
|
}
|
|
426
599
|
|
|
427
600
|
if (kind === "hooked-block") {
|
|
@@ -449,10 +622,19 @@ export async function executeAddCommand({
|
|
|
449
622
|
cwd,
|
|
450
623
|
position,
|
|
451
624
|
});
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
625
|
+
const payload = buildAddCompletionPayload({
|
|
626
|
+
kind: "hooked-block",
|
|
627
|
+
projectDir: result.projectDir,
|
|
628
|
+
values: {
|
|
629
|
+
anchorBlockName: result.anchorBlockName,
|
|
630
|
+
blockSlug: result.blockSlug,
|
|
631
|
+
position: result.position,
|
|
632
|
+
},
|
|
633
|
+
});
|
|
634
|
+
if (emitOutput) {
|
|
635
|
+
printCompletionPayload(payload, { printLine });
|
|
636
|
+
}
|
|
637
|
+
return payload;
|
|
456
638
|
}
|
|
457
639
|
|
|
458
640
|
if (kind !== "block") {
|
|
@@ -485,9 +667,18 @@ export async function executeAddCommand({
|
|
|
485
667
|
| "compound",
|
|
486
668
|
});
|
|
487
669
|
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
670
|
+
const payload = buildAddCompletionPayload({
|
|
671
|
+
kind: "block",
|
|
672
|
+
projectDir: result.projectDir,
|
|
673
|
+
values: {
|
|
674
|
+
blockSlugs: result.blockSlugs.join(", "),
|
|
675
|
+
templateId: result.templateId,
|
|
676
|
+
},
|
|
677
|
+
});
|
|
678
|
+
if (emitOutput) {
|
|
679
|
+
printCompletionPayload(payload, { printLine });
|
|
680
|
+
}
|
|
681
|
+
return payload;
|
|
491
682
|
}
|
|
492
683
|
|
|
493
684
|
export async function executeTemplatesCommand(
|
|
@@ -568,7 +759,7 @@ export async function executeMigrateCommand({
|
|
|
568
759
|
flags,
|
|
569
760
|
prompt,
|
|
570
761
|
renderLine,
|
|
571
|
-
}: MigrateExecutionInput): Promise<void> {
|
|
762
|
+
}: MigrateExecutionInput): Promise<AlternateBufferCompletionPayload | void> {
|
|
572
763
|
const { formatMigrationHelpText, parseMigrationArgs, runMigrationCommand } =
|
|
573
764
|
await loadMigrationsRuntime();
|
|
574
765
|
if (!command) {
|
|
@@ -599,10 +790,31 @@ export async function executeMigrateCommand({
|
|
|
599
790
|
pushFlag(argv, "seed", readOptionalLooseStringFlag(flags, "seed"));
|
|
600
791
|
|
|
601
792
|
const parsed = parseMigrationArgs(argv);
|
|
602
|
-
|
|
793
|
+
const lines: string[] | null = renderLine ? [] : null;
|
|
794
|
+
const captureLine = (line: string) => {
|
|
795
|
+
lines?.push(line);
|
|
796
|
+
if (renderLine) {
|
|
797
|
+
renderLine(line);
|
|
798
|
+
return;
|
|
799
|
+
}
|
|
800
|
+
console.log(line);
|
|
801
|
+
};
|
|
802
|
+
const result = await runMigrationCommand(parsed, cwd, {
|
|
603
803
|
prompt,
|
|
604
|
-
renderLine,
|
|
804
|
+
renderLine: captureLine,
|
|
605
805
|
});
|
|
806
|
+
if (renderLine) {
|
|
807
|
+
return result && typeof result === "object" && "cancelled" in result && result.cancelled === true
|
|
808
|
+
? undefined
|
|
809
|
+
: buildMigrationCompletionPayload({
|
|
810
|
+
command: parsed.command ?? "plan",
|
|
811
|
+
lines: lines ?? [],
|
|
812
|
+
});
|
|
813
|
+
}
|
|
814
|
+
|
|
815
|
+
if (result && typeof result === "object" && "cancelled" in result && result.cancelled === true) {
|
|
816
|
+
return;
|
|
817
|
+
}
|
|
606
818
|
}
|
|
607
819
|
|
|
608
820
|
export { listTemplates };
|
package/src/ui/add-flow.tsx
CHANGED
|
@@ -20,7 +20,8 @@ import {
|
|
|
20
20
|
sanitizeAddSubmitValues,
|
|
21
21
|
} from "./add-flow-model";
|
|
22
22
|
import {
|
|
23
|
-
|
|
23
|
+
FirstPartyCompletionViewport,
|
|
24
|
+
FirstPartyFormViewport,
|
|
24
25
|
FirstPartySelectField,
|
|
25
26
|
FirstPartyTextField,
|
|
26
27
|
} from "./first-party-form";
|
|
@@ -137,7 +138,7 @@ function AddFlowFields({
|
|
|
137
138
|
}: {
|
|
138
139
|
workspaceBlockOptions: WorkspaceBlockOption[];
|
|
139
140
|
}) {
|
|
140
|
-
const { activeFieldName, values } = useFormContext();
|
|
141
|
+
const { activeFieldName, isSubmitting, values } = useFormContext();
|
|
141
142
|
const { height: terminalHeight = 24 } = useTerminalDimensions();
|
|
142
143
|
const addValues = values as Partial<AddFlowValues>;
|
|
143
144
|
const kind = addValues.kind ?? "block";
|
|
@@ -160,8 +161,14 @@ function AddFlowFields({
|
|
|
160
161
|
const variationBlockUsesSelect = kind === "variation" && workspaceBlockOptions.length > 0;
|
|
161
162
|
|
|
162
163
|
return createElement(
|
|
163
|
-
|
|
164
|
-
{
|
|
164
|
+
FirstPartyFormViewport,
|
|
165
|
+
{
|
|
166
|
+
isSubmitting,
|
|
167
|
+
scrollTop,
|
|
168
|
+
submittingDescription: "Applying your workspace changes...",
|
|
169
|
+
submittingTitle: "Updating workspace...",
|
|
170
|
+
viewportHeight,
|
|
171
|
+
},
|
|
165
172
|
[
|
|
166
173
|
createElement(FirstPartySelectField, {
|
|
167
174
|
...getWrappedFieldNeighbors(orderedVisibleFields, "kind"),
|
|
@@ -257,9 +264,11 @@ function AddFlowFields({
|
|
|
257
264
|
}
|
|
258
265
|
|
|
259
266
|
export function AddFlow({ cwd, initialValues }: AddFlowProps) {
|
|
260
|
-
const { handleCancel, handleFailure, handleSubmit } =
|
|
267
|
+
const { completion, handleCancel, handleFailure, handleSubmit, status } =
|
|
268
|
+
useAlternateBufferLifecycle(
|
|
261
269
|
"wp-typia add failed",
|
|
262
|
-
|
|
270
|
+
);
|
|
271
|
+
const { height: terminalHeight = 24 } = useTerminalDimensions();
|
|
263
272
|
const [workspaceBlockOptions, setWorkspaceBlockOptions] = useState<WorkspaceBlockOption[]>([]);
|
|
264
273
|
|
|
265
274
|
useEffect(() => {
|
|
@@ -283,6 +292,13 @@ export function AddFlow({ cwd, initialValues }: AddFlowProps) {
|
|
|
283
292
|
};
|
|
284
293
|
}, [cwd, handleFailure]);
|
|
285
294
|
|
|
295
|
+
if (status === "completed" && completion) {
|
|
296
|
+
return createElement(FirstPartyCompletionViewport, {
|
|
297
|
+
completion,
|
|
298
|
+
viewportHeight: getAddViewportHeight(terminalHeight),
|
|
299
|
+
});
|
|
300
|
+
}
|
|
301
|
+
|
|
286
302
|
return (
|
|
287
303
|
<Form
|
|
288
304
|
initialValues={initialValues}
|
|
@@ -290,8 +306,9 @@ export function AddFlow({ cwd, initialValues }: AddFlowProps) {
|
|
|
290
306
|
onSubmit={async (values) =>
|
|
291
307
|
handleSubmit(async () => {
|
|
292
308
|
const flags = sanitizeAddSubmitValues(values);
|
|
293
|
-
|
|
309
|
+
return executeAddCommand({
|
|
294
310
|
cwd,
|
|
311
|
+
emitOutput: false,
|
|
295
312
|
flags,
|
|
296
313
|
kind: values.kind,
|
|
297
314
|
name: typeof flags.name === "string" ? flags.name : undefined,
|
|
@@ -1,13 +1,25 @@
|
|
|
1
|
-
import { useCallback } from "react";
|
|
1
|
+
import { useCallback, useState } from "react";
|
|
2
2
|
|
|
3
3
|
import { useRuntime } from "@bunli/runtime/app";
|
|
4
4
|
import { useKeyboard } from "@bunli/tui";
|
|
5
5
|
|
|
6
6
|
type AlternateBufferKeyEvent = {
|
|
7
7
|
ctrl?: boolean;
|
|
8
|
+
sequence?: string;
|
|
8
9
|
name?: string;
|
|
9
10
|
};
|
|
10
11
|
|
|
12
|
+
export type AlternateBufferCompletionPayload = {
|
|
13
|
+
title: string;
|
|
14
|
+
preambleLines?: string[];
|
|
15
|
+
summaryLines?: string[];
|
|
16
|
+
nextSteps?: string[];
|
|
17
|
+
optionalTitle?: string;
|
|
18
|
+
optionalLines?: string[];
|
|
19
|
+
optionalNote?: string;
|
|
20
|
+
warningLines?: string[];
|
|
21
|
+
};
|
|
22
|
+
|
|
11
23
|
type AlternateBufferFailureOptions = {
|
|
12
24
|
context: string;
|
|
13
25
|
error: unknown;
|
|
@@ -16,12 +28,16 @@ type AlternateBufferFailureOptions = {
|
|
|
16
28
|
};
|
|
17
29
|
|
|
18
30
|
type RunAlternateBufferActionOptions = {
|
|
19
|
-
action: () => Promise<
|
|
31
|
+
action: () => Promise<unknown>;
|
|
20
32
|
context: string;
|
|
21
33
|
exit: () => void;
|
|
34
|
+
exitOnSuccess?: boolean;
|
|
22
35
|
log?: (message: string) => void;
|
|
36
|
+
onSuccess?: (result: unknown) => void;
|
|
23
37
|
};
|
|
24
38
|
|
|
39
|
+
type AlternateBufferLifecycleStatus = "editing" | "submitting" | "completed";
|
|
40
|
+
|
|
25
41
|
export function describeAlternateBufferFailure(context: string, error: unknown): string {
|
|
26
42
|
const message = error instanceof Error ? error.message : String(error);
|
|
27
43
|
return `${context}: ${message}`;
|
|
@@ -31,6 +47,10 @@ export function isAlternateBufferExitKey(key: AlternateBufferKeyEvent): boolean
|
|
|
31
47
|
return key.name === "q" || (key.ctrl === true && key.name === "c");
|
|
32
48
|
}
|
|
33
49
|
|
|
50
|
+
export function isAlternateBufferCompletionKey(key: AlternateBufferKeyEvent): boolean {
|
|
51
|
+
return key.name === "enter" || key.sequence === "\r" || key.sequence === "\n";
|
|
52
|
+
}
|
|
53
|
+
|
|
34
54
|
export function reportAlternateBufferFailure({
|
|
35
55
|
context,
|
|
36
56
|
error,
|
|
@@ -46,11 +66,16 @@ export async function runAlternateBufferAction({
|
|
|
46
66
|
action,
|
|
47
67
|
context,
|
|
48
68
|
exit,
|
|
69
|
+
exitOnSuccess = true,
|
|
49
70
|
log = console.error,
|
|
71
|
+
onSuccess,
|
|
50
72
|
}: RunAlternateBufferActionOptions): Promise<void> {
|
|
51
73
|
try {
|
|
52
|
-
await action();
|
|
53
|
-
|
|
74
|
+
const result = await action();
|
|
75
|
+
onSuccess?.(result);
|
|
76
|
+
if (exitOnSuccess) {
|
|
77
|
+
exit();
|
|
78
|
+
}
|
|
54
79
|
} catch (error) {
|
|
55
80
|
reportAlternateBufferFailure({ context, error, exit, log });
|
|
56
81
|
}
|
|
@@ -98,32 +123,75 @@ export function useAlternateBufferExitKeys(options: {
|
|
|
98
123
|
});
|
|
99
124
|
}
|
|
100
125
|
|
|
126
|
+
export function useAlternateBufferCompletionKeys(options: {
|
|
127
|
+
enabled?: boolean;
|
|
128
|
+
exit?: () => void;
|
|
129
|
+
} = {}): void {
|
|
130
|
+
const runtime = useRuntime();
|
|
131
|
+
const exit = options.exit ?? (() => runtime.exit());
|
|
132
|
+
const enabled = options.enabled ?? false;
|
|
133
|
+
|
|
134
|
+
useKeyboard((key: AlternateBufferKeyEvent) => {
|
|
135
|
+
if (!enabled) {
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
if (isAlternateBufferCompletionKey(key) || isAlternateBufferExitKey(key)) {
|
|
140
|
+
exit();
|
|
141
|
+
}
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
function isAlternateBufferCompletionPayload(
|
|
146
|
+
value: unknown,
|
|
147
|
+
): value is AlternateBufferCompletionPayload {
|
|
148
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
149
|
+
return false;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
const candidate = value as { title?: unknown };
|
|
153
|
+
return typeof candidate.title === "string" && candidate.title.trim().length > 0;
|
|
154
|
+
}
|
|
155
|
+
|
|
101
156
|
export function useAlternateBufferLifecycle(
|
|
102
157
|
context: string,
|
|
103
158
|
options: {
|
|
104
159
|
enableExitKeys?: boolean;
|
|
105
160
|
} = {},
|
|
106
161
|
): {
|
|
162
|
+
completion: AlternateBufferCompletionPayload | null;
|
|
107
163
|
handleCancel: () => void;
|
|
108
164
|
handleFailure: (error: unknown) => void;
|
|
109
|
-
handleSubmit: (action: () => Promise<void>) => Promise<void>;
|
|
165
|
+
handleSubmit: (action: () => Promise<AlternateBufferCompletionPayload | void>) => Promise<void>;
|
|
166
|
+
status: AlternateBufferLifecycleStatus;
|
|
110
167
|
} {
|
|
111
168
|
const runtime = useRuntime();
|
|
169
|
+
const [completion, setCompletion] = useState<AlternateBufferCompletionPayload | null>(null);
|
|
170
|
+
const [status, setStatus] = useState<AlternateBufferLifecycleStatus>("editing");
|
|
112
171
|
const exit = useCallback(() => {
|
|
113
172
|
runtime.exit();
|
|
114
173
|
}, [runtime]);
|
|
115
174
|
|
|
116
175
|
useAlternateBufferExitKeys({
|
|
117
|
-
enabled: options.enableExitKeys ?? true,
|
|
176
|
+
enabled: (options.enableExitKeys ?? true) && status !== "completed",
|
|
177
|
+
exit,
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
useAlternateBufferCompletionKeys({
|
|
181
|
+
enabled: status === "completed",
|
|
118
182
|
exit,
|
|
119
183
|
});
|
|
120
184
|
|
|
121
185
|
const handleCancel = useCallback(() => {
|
|
186
|
+
setCompletion(null);
|
|
187
|
+
setStatus("editing");
|
|
122
188
|
exit();
|
|
123
189
|
}, [exit]);
|
|
124
190
|
|
|
125
191
|
const handleFailure = useCallback(
|
|
126
192
|
(error: unknown) => {
|
|
193
|
+
setCompletion(null);
|
|
194
|
+
setStatus("editing");
|
|
127
195
|
reportAlternateBufferFailure({
|
|
128
196
|
context,
|
|
129
197
|
error,
|
|
@@ -134,19 +202,37 @@ export function useAlternateBufferLifecycle(
|
|
|
134
202
|
);
|
|
135
203
|
|
|
136
204
|
const handleSubmit = useCallback(
|
|
137
|
-
async (action: () => Promise<void>) => {
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
205
|
+
async (action: () => Promise<AlternateBufferCompletionPayload | void>) => {
|
|
206
|
+
setCompletion(null);
|
|
207
|
+
setStatus("submitting");
|
|
208
|
+
|
|
209
|
+
try {
|
|
210
|
+
const result = await action();
|
|
211
|
+
if (isAlternateBufferCompletionPayload(result)) {
|
|
212
|
+
setCompletion(result);
|
|
213
|
+
setStatus("completed");
|
|
214
|
+
return;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
exit();
|
|
218
|
+
} catch (error) {
|
|
219
|
+
setCompletion(null);
|
|
220
|
+
setStatus("editing");
|
|
221
|
+
reportAlternateBufferFailure({
|
|
222
|
+
context,
|
|
223
|
+
error,
|
|
224
|
+
exit,
|
|
225
|
+
});
|
|
226
|
+
}
|
|
143
227
|
},
|
|
144
228
|
[context, exit],
|
|
145
229
|
);
|
|
146
230
|
|
|
147
231
|
return {
|
|
232
|
+
completion,
|
|
148
233
|
handleCancel,
|
|
149
234
|
handleFailure,
|
|
150
235
|
handleSubmit,
|
|
236
|
+
status,
|
|
151
237
|
};
|
|
152
238
|
}
|
package/src/ui/create-flow.tsx
CHANGED
|
@@ -21,7 +21,8 @@ import {
|
|
|
21
21
|
} from "./create-flow-model";
|
|
22
22
|
import {
|
|
23
23
|
FirstPartyCheckboxField,
|
|
24
|
-
|
|
24
|
+
FirstPartyCompletionViewport,
|
|
25
|
+
FirstPartyFormViewport,
|
|
25
26
|
FirstPartySelectField,
|
|
26
27
|
FirstPartyTextField,
|
|
27
28
|
} from "./first-party-form";
|
|
@@ -70,7 +71,7 @@ type CreateSelectFieldName = {
|
|
|
70
71
|
}[keyof CreateFlowValues];
|
|
71
72
|
|
|
72
73
|
function CreateFlowFields() {
|
|
73
|
-
const { activeFieldName, values } = useFormContext();
|
|
74
|
+
const { activeFieldName, isSubmitting, values } = useFormContext();
|
|
74
75
|
const { height: terminalHeight = 24 } = useTerminalDimensions();
|
|
75
76
|
const createValues = values as Partial<CreateFlowValues>;
|
|
76
77
|
const template = createValues.template;
|
|
@@ -88,8 +89,14 @@ function CreateFlowFields() {
|
|
|
88
89
|
);
|
|
89
90
|
|
|
90
91
|
return createElement(
|
|
91
|
-
|
|
92
|
-
{
|
|
92
|
+
FirstPartyFormViewport,
|
|
93
|
+
{
|
|
94
|
+
isSubmitting,
|
|
95
|
+
scrollTop,
|
|
96
|
+
submittingDescription: "Preparing your wp-typia project files...",
|
|
97
|
+
submittingTitle: "Creating project...",
|
|
98
|
+
viewportHeight,
|
|
99
|
+
},
|
|
93
100
|
[
|
|
94
101
|
createElement(FirstPartyTextField, {
|
|
95
102
|
...getWrappedFieldNeighbors(visibleFields, "project-dir"),
|
|
@@ -161,7 +168,10 @@ function CreateFlowFields() {
|
|
|
161
168
|
}
|
|
162
169
|
|
|
163
170
|
export function CreateFlow({ cwd, initialValues }: CreateFlowProps) {
|
|
164
|
-
const { handleCancel, handleSubmit } = useAlternateBufferLifecycle(
|
|
171
|
+
const { completion, handleCancel, handleSubmit, status } = useAlternateBufferLifecycle(
|
|
172
|
+
"wp-typia create failed",
|
|
173
|
+
);
|
|
174
|
+
const { height: terminalHeight = 24 } = useTerminalDimensions();
|
|
165
175
|
const defaultPrompt = {
|
|
166
176
|
close() {},
|
|
167
177
|
select<T extends string>(_message: string, options: Array<{ value: T }>, defaultValue = 1) {
|
|
@@ -173,6 +183,13 @@ export function CreateFlow({ cwd, initialValues }: CreateFlowProps) {
|
|
|
173
183
|
},
|
|
174
184
|
};
|
|
175
185
|
|
|
186
|
+
if (status === "completed" && completion) {
|
|
187
|
+
return createElement(FirstPartyCompletionViewport, {
|
|
188
|
+
completion,
|
|
189
|
+
viewportHeight: getCreateViewportHeight(terminalHeight),
|
|
190
|
+
});
|
|
191
|
+
}
|
|
192
|
+
|
|
176
193
|
return (
|
|
177
194
|
<Form
|
|
178
195
|
initialValues={initialValues}
|
|
@@ -180,8 +197,9 @@ export function CreateFlow({ cwd, initialValues }: CreateFlowProps) {
|
|
|
180
197
|
onSubmit={async (values) =>
|
|
181
198
|
handleSubmit(async () => {
|
|
182
199
|
const flags = sanitizeCreateSubmitValues(values);
|
|
183
|
-
|
|
200
|
+
return executeCreateCommand({
|
|
184
201
|
cwd,
|
|
202
|
+
emitOutput: false,
|
|
185
203
|
flags,
|
|
186
204
|
interactive: true,
|
|
187
205
|
projectDir: values["project-dir"],
|
|
@@ -10,6 +10,7 @@ import {
|
|
|
10
10
|
|
|
11
11
|
import { useScopedKeyboard } from "@bunli/runtime/app";
|
|
12
12
|
import {
|
|
13
|
+
Spinner,
|
|
13
14
|
type SelectOption,
|
|
14
15
|
createKeyMatcher,
|
|
15
16
|
useFormContext,
|
|
@@ -23,6 +24,7 @@ import {
|
|
|
23
24
|
FIRST_PARTY_SELECT_FIELD_CONTROL_HEIGHT,
|
|
24
25
|
FIRST_PARTY_SELECT_FIELD_LABEL_GAP,
|
|
25
26
|
} from "./first-party-form-model";
|
|
27
|
+
import type { AlternateBufferCompletionPayload } from "./alternate-buffer-lifecycle";
|
|
26
28
|
|
|
27
29
|
const checkboxKeymap = createKeyMatcher({
|
|
28
30
|
toggle: ["space", "enter"],
|
|
@@ -96,6 +98,30 @@ function isCheckboxToggleKey(key: Parameters<typeof checkboxKeymap.match>[1]) {
|
|
|
96
98
|
);
|
|
97
99
|
}
|
|
98
100
|
|
|
101
|
+
function isCompletionLineDownKey(key: Parameters<typeof checkboxKeymap.match>[1]) {
|
|
102
|
+
return key.name === "down" || key.sequence === "\x1b[B";
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function isCompletionLineUpKey(key: Parameters<typeof checkboxKeymap.match>[1]) {
|
|
106
|
+
return key.name === "up" || key.sequence === "\x1b[A";
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function isCompletionPageDownKey(key: Parameters<typeof checkboxKeymap.match>[1]) {
|
|
110
|
+
return key.name === "pagedown" || key.sequence === "\x1b[6~" || key.sequence === " ";
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function isCompletionPageUpKey(key: Parameters<typeof checkboxKeymap.match>[1]) {
|
|
114
|
+
return key.name === "pageup" || key.sequence === "\x1b[5~";
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function isCompletionHomeKey(key: Parameters<typeof checkboxKeymap.match>[1]) {
|
|
118
|
+
return key.name === "home" || key.sequence === "\x1b[H" || key.sequence === "\x1bOH";
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function isCompletionEndKey(key: Parameters<typeof checkboxKeymap.match>[1]) {
|
|
122
|
+
return key.name === "end" || key.sequence === "\x1b[F" || key.sequence === "\x1bOF";
|
|
123
|
+
}
|
|
124
|
+
|
|
99
125
|
function useFirstPartyFieldNavigation(options: {
|
|
100
126
|
focused: boolean;
|
|
101
127
|
keyboardScopeId: string;
|
|
@@ -458,3 +484,237 @@ export function FirstPartyScrollBox({
|
|
|
458
484
|
),
|
|
459
485
|
);
|
|
460
486
|
}
|
|
487
|
+
|
|
488
|
+
export function FirstPartySubmittingSurface({
|
|
489
|
+
description = "Please wait while wp-typia finishes this command.",
|
|
490
|
+
title = "Submitting...",
|
|
491
|
+
viewportHeight,
|
|
492
|
+
}: {
|
|
493
|
+
description?: string;
|
|
494
|
+
title?: string;
|
|
495
|
+
viewportHeight: number;
|
|
496
|
+
}) {
|
|
497
|
+
const { tokens } = useTuiTheme();
|
|
498
|
+
|
|
499
|
+
return createElement(
|
|
500
|
+
"box",
|
|
501
|
+
{
|
|
502
|
+
border: true,
|
|
503
|
+
height: viewportHeight,
|
|
504
|
+
width: "100%",
|
|
505
|
+
"data-form-surface": "submitting",
|
|
506
|
+
style: {
|
|
507
|
+
alignItems: "center",
|
|
508
|
+
borderColor: tokens.borderMuted,
|
|
509
|
+
flexDirection: "column",
|
|
510
|
+
gap: 1,
|
|
511
|
+
justifyContent: "center",
|
|
512
|
+
},
|
|
513
|
+
},
|
|
514
|
+
createElement(Spinner, {
|
|
515
|
+
title,
|
|
516
|
+
variant: "dot",
|
|
517
|
+
}),
|
|
518
|
+
createElement("text", {
|
|
519
|
+
content: description,
|
|
520
|
+
fg: tokens.textMuted,
|
|
521
|
+
}),
|
|
522
|
+
);
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
export function FirstPartyFormViewport({
|
|
526
|
+
children,
|
|
527
|
+
isSubmitting = false,
|
|
528
|
+
scrollTop,
|
|
529
|
+
submittingDescription,
|
|
530
|
+
submittingTitle,
|
|
531
|
+
viewportHeight,
|
|
532
|
+
}: {
|
|
533
|
+
children?: ReactNode;
|
|
534
|
+
isSubmitting?: boolean;
|
|
535
|
+
scrollTop: number;
|
|
536
|
+
submittingDescription?: string;
|
|
537
|
+
submittingTitle?: string;
|
|
538
|
+
viewportHeight: number;
|
|
539
|
+
}) {
|
|
540
|
+
if (isSubmitting) {
|
|
541
|
+
return createElement(FirstPartySubmittingSurface, {
|
|
542
|
+
description: submittingDescription,
|
|
543
|
+
title: submittingTitle,
|
|
544
|
+
viewportHeight,
|
|
545
|
+
});
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
return createElement(FirstPartyScrollBox, {
|
|
549
|
+
scrollTop,
|
|
550
|
+
viewportHeight,
|
|
551
|
+
children,
|
|
552
|
+
});
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
export function FirstPartyCompletionViewport({
|
|
556
|
+
completion,
|
|
557
|
+
viewportHeight,
|
|
558
|
+
}: {
|
|
559
|
+
completion: AlternateBufferCompletionPayload;
|
|
560
|
+
viewportHeight: number;
|
|
561
|
+
}) {
|
|
562
|
+
const { tokens } = useTuiTheme();
|
|
563
|
+
const reactScopeId = useId();
|
|
564
|
+
const keyboardScopeId = `first-party-completion:${reactScopeId}`;
|
|
565
|
+
const bodyHeight = Math.max(4, viewportHeight - 3);
|
|
566
|
+
const bodyRef = useRef<{ scrollTop: number } | null>(null);
|
|
567
|
+
|
|
568
|
+
const setCompletionScrollTop = useCallback((nextScrollTop: number) => {
|
|
569
|
+
if (!bodyRef.current) {
|
|
570
|
+
return false;
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
bodyRef.current.scrollTop = Math.max(0, nextScrollTop);
|
|
574
|
+
return true;
|
|
575
|
+
}, []);
|
|
576
|
+
|
|
577
|
+
const adjustCompletionScrollTop = useCallback(
|
|
578
|
+
(delta: number) => {
|
|
579
|
+
if (!bodyRef.current) {
|
|
580
|
+
return false;
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
bodyRef.current.scrollTop = Math.max(0, bodyRef.current.scrollTop + delta);
|
|
584
|
+
return true;
|
|
585
|
+
},
|
|
586
|
+
[],
|
|
587
|
+
);
|
|
588
|
+
|
|
589
|
+
useScopedKeyboard(
|
|
590
|
+
keyboardScopeId,
|
|
591
|
+
(key) => {
|
|
592
|
+
if (isCompletionLineDownKey(key)) {
|
|
593
|
+
return adjustCompletionScrollTop(1);
|
|
594
|
+
}
|
|
595
|
+
if (isCompletionLineUpKey(key)) {
|
|
596
|
+
return adjustCompletionScrollTop(-1);
|
|
597
|
+
}
|
|
598
|
+
if (isCompletionPageDownKey(key)) {
|
|
599
|
+
return adjustCompletionScrollTop(Math.max(1, bodyHeight - 1));
|
|
600
|
+
}
|
|
601
|
+
if (isCompletionPageUpKey(key)) {
|
|
602
|
+
return adjustCompletionScrollTop(-Math.max(1, bodyHeight - 1));
|
|
603
|
+
}
|
|
604
|
+
if (isCompletionHomeKey(key)) {
|
|
605
|
+
return setCompletionScrollTop(0);
|
|
606
|
+
}
|
|
607
|
+
if (isCompletionEndKey(key)) {
|
|
608
|
+
return setCompletionScrollTop(Number.MAX_SAFE_INTEGER);
|
|
609
|
+
}
|
|
610
|
+
|
|
611
|
+
return false;
|
|
612
|
+
},
|
|
613
|
+
{ active: true },
|
|
614
|
+
);
|
|
615
|
+
|
|
616
|
+
return createElement(
|
|
617
|
+
"box",
|
|
618
|
+
{
|
|
619
|
+
border: true,
|
|
620
|
+
height: viewportHeight,
|
|
621
|
+
width: "100%",
|
|
622
|
+
"data-form-surface": "completed",
|
|
623
|
+
style: {
|
|
624
|
+
borderColor: tokens.borderMuted,
|
|
625
|
+
flexDirection: "column",
|
|
626
|
+
},
|
|
627
|
+
},
|
|
628
|
+
createElement(
|
|
629
|
+
"scrollbox",
|
|
630
|
+
{
|
|
631
|
+
ref: bodyRef,
|
|
632
|
+
height: bodyHeight,
|
|
633
|
+
scrollY: true,
|
|
634
|
+
scrollbarOptions: {
|
|
635
|
+
visible: true,
|
|
636
|
+
trackOptions: {
|
|
637
|
+
backgroundColor: tokens.backgroundMuted,
|
|
638
|
+
foregroundColor: tokens.borderMuted,
|
|
639
|
+
},
|
|
640
|
+
},
|
|
641
|
+
viewportOptions: { width: "100%" },
|
|
642
|
+
contentOptions: { width: "100%" },
|
|
643
|
+
},
|
|
644
|
+
createElement(
|
|
645
|
+
"box",
|
|
646
|
+
{
|
|
647
|
+
width: "100%",
|
|
648
|
+
style: {
|
|
649
|
+
flexDirection: "column",
|
|
650
|
+
gap: 1,
|
|
651
|
+
},
|
|
652
|
+
},
|
|
653
|
+
createElement("text", {
|
|
654
|
+
content: completion.title,
|
|
655
|
+
fg: tokens.accent,
|
|
656
|
+
}),
|
|
657
|
+
...(completion.preambleLines ?? []).map((line, index) =>
|
|
658
|
+
createElement("text", {
|
|
659
|
+
content: line,
|
|
660
|
+
fg: tokens.textMuted,
|
|
661
|
+
key: `preamble:${index}`,
|
|
662
|
+
}),
|
|
663
|
+
),
|
|
664
|
+
...(completion.warningLines ?? []).map((line, index) =>
|
|
665
|
+
createElement("text", {
|
|
666
|
+
content: `⚠️ ${line}`,
|
|
667
|
+
fg: tokens.textWarning,
|
|
668
|
+
key: `warning:${index}`,
|
|
669
|
+
}),
|
|
670
|
+
),
|
|
671
|
+
...(completion.summaryLines ?? []).map((line, index) =>
|
|
672
|
+
createElement("text", {
|
|
673
|
+
content: line,
|
|
674
|
+
fg: tokens.textPrimary,
|
|
675
|
+
key: `summary:${index}`,
|
|
676
|
+
}),
|
|
677
|
+
),
|
|
678
|
+
(completion.nextSteps?.length ?? 0) > 0
|
|
679
|
+
? createElement("text", {
|
|
680
|
+
content: "Next steps:",
|
|
681
|
+
fg: tokens.textPrimary,
|
|
682
|
+
key: "next-steps:title",
|
|
683
|
+
})
|
|
684
|
+
: null,
|
|
685
|
+
...(completion.nextSteps ?? []).map((line, index) =>
|
|
686
|
+
createElement("text", {
|
|
687
|
+
content: ` ${line}`,
|
|
688
|
+
fg: tokens.textPrimary,
|
|
689
|
+
key: `next-step:${index}`,
|
|
690
|
+
}),
|
|
691
|
+
),
|
|
692
|
+
(completion.optionalLines?.length ?? 0) > 0
|
|
693
|
+
? createElement("text", {
|
|
694
|
+
content: completion.optionalTitle ?? "Optional:",
|
|
695
|
+
fg: tokens.textPrimary,
|
|
696
|
+
key: "optional:title",
|
|
697
|
+
})
|
|
698
|
+
: null,
|
|
699
|
+
...(completion.optionalLines ?? []).map((line, index) =>
|
|
700
|
+
createElement("text", {
|
|
701
|
+
content: ` ${line}`,
|
|
702
|
+
fg: tokens.textMuted,
|
|
703
|
+
key: `optional:${index}`,
|
|
704
|
+
}),
|
|
705
|
+
),
|
|
706
|
+
completion.optionalNote
|
|
707
|
+
? createElement("text", {
|
|
708
|
+
content: `Note: ${completion.optionalNote}`,
|
|
709
|
+
fg: tokens.textMuted,
|
|
710
|
+
key: "optional:note",
|
|
711
|
+
})
|
|
712
|
+
: null,
|
|
713
|
+
),
|
|
714
|
+
),
|
|
715
|
+
createElement("text", {
|
|
716
|
+
content: "PgUp/PgDn | ↑/↓ | Home/End | Enter: close | q: exit | Ctrl+C: quit",
|
|
717
|
+
fg: tokens.textMuted,
|
|
718
|
+
}),
|
|
719
|
+
);
|
|
720
|
+
}
|
package/src/ui/migrate-flow.tsx
CHANGED
|
@@ -19,7 +19,8 @@ import {
|
|
|
19
19
|
} from "./migrate-flow-model";
|
|
20
20
|
import {
|
|
21
21
|
FirstPartyCheckboxField,
|
|
22
|
-
|
|
22
|
+
FirstPartyCompletionViewport,
|
|
23
|
+
FirstPartyFormViewport,
|
|
23
24
|
FirstPartySelectField,
|
|
24
25
|
FirstPartyTextField,
|
|
25
26
|
} from "./first-party-form";
|
|
@@ -76,7 +77,7 @@ type MigrateCheckboxFieldName = {
|
|
|
76
77
|
}[keyof MigrateFlowValues];
|
|
77
78
|
|
|
78
79
|
function MigrateFlowFields() {
|
|
79
|
-
const { activeFieldName, values } = useFormContext();
|
|
80
|
+
const { activeFieldName, isSubmitting, values } = useFormContext();
|
|
80
81
|
const { height: terminalHeight = 24 } = useTerminalDimensions();
|
|
81
82
|
const migrateValues = values as Partial<MigrateFlowValues>;
|
|
82
83
|
const command = migrateValues.command ?? "plan";
|
|
@@ -98,8 +99,14 @@ function MigrateFlowFields() {
|
|
|
98
99
|
);
|
|
99
100
|
|
|
100
101
|
return createElement(
|
|
101
|
-
|
|
102
|
-
{
|
|
102
|
+
FirstPartyFormViewport,
|
|
103
|
+
{
|
|
104
|
+
isSubmitting,
|
|
105
|
+
scrollTop,
|
|
106
|
+
submittingDescription: "Running the selected migration workflow...",
|
|
107
|
+
submittingTitle: "Running migration...",
|
|
108
|
+
viewportHeight,
|
|
109
|
+
},
|
|
103
110
|
[
|
|
104
111
|
createElement(FirstPartySelectField, {
|
|
105
112
|
...getWrappedFieldNeighbors(orderedVisibleFields, "command"),
|
|
@@ -180,7 +187,17 @@ function MigrateFlowFields() {
|
|
|
180
187
|
}
|
|
181
188
|
|
|
182
189
|
export function MigrateFlow({ cwd, initialValues }: MigrateFlowProps) {
|
|
183
|
-
const { handleCancel, handleSubmit } = useAlternateBufferLifecycle(
|
|
190
|
+
const { completion, handleCancel, handleSubmit, status } = useAlternateBufferLifecycle(
|
|
191
|
+
"wp-typia migrate failed",
|
|
192
|
+
);
|
|
193
|
+
const { height: terminalHeight = 24 } = useTerminalDimensions();
|
|
194
|
+
|
|
195
|
+
if (status === "completed" && completion) {
|
|
196
|
+
return createElement(FirstPartyCompletionViewport, {
|
|
197
|
+
completion,
|
|
198
|
+
viewportHeight: getMigrateViewportHeight(terminalHeight),
|
|
199
|
+
});
|
|
200
|
+
}
|
|
184
201
|
|
|
185
202
|
return (
|
|
186
203
|
<Form
|
|
@@ -189,10 +206,11 @@ export function MigrateFlow({ cwd, initialValues }: MigrateFlowProps) {
|
|
|
189
206
|
onSubmit={async (values) =>
|
|
190
207
|
handleSubmit(async () => {
|
|
191
208
|
const flags = sanitizeMigrateSubmitValues(values);
|
|
192
|
-
|
|
209
|
+
return executeMigrateCommand({
|
|
193
210
|
command: values.command,
|
|
194
211
|
cwd,
|
|
195
212
|
flags,
|
|
213
|
+
renderLine: () => undefined,
|
|
196
214
|
});
|
|
197
215
|
})
|
|
198
216
|
}
|