wp-typia 0.16.9 → 0.16.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "wp-typia",
3
- "version": "0.16.9",
3
+ "version": "0.16.11",
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.4",
68
- "@wp-typia/project-tools": "0.16.8",
68
+ "@wp-typia/project-tools": "0.16.10",
69
69
  "better-result": "^2.7.0",
70
70
  "react": "^19.2.5",
71
71
  "react-dom": "^19.2.5",
package/src/cli.ts CHANGED
@@ -73,6 +73,15 @@ function resolveGeneratedMetadataPath(moduleUrl: string): string {
73
73
  return fileURLToPath(new URL("../.bunli/commands.gen.ts", moduleUrl));
74
74
  }
75
75
 
76
+ async function formatCliError(error: unknown): Promise<string> {
77
+ try {
78
+ const { formatCliDiagnosticError } = await import("@wp-typia/project-tools/cli-diagnostics");
79
+ return formatCliDiagnosticError(error);
80
+ } catch {
81
+ return error instanceof Error ? error.message : String(error);
82
+ }
83
+ }
84
+
76
85
  export async function createWpTypiaCli(options: {
77
86
  configOverridePath?: string;
78
87
  } = {}): Promise<CLI> {
@@ -117,10 +126,14 @@ export async function main(argv = process.argv.slice(2)): Promise<void> {
117
126
  }
118
127
 
119
128
  if (import.meta.main) {
120
- void main().catch((error) => {
121
- console.error(error instanceof Error ? error.message : error);
122
- process.exit(1);
123
- });
129
+ void (async () => {
130
+ try {
131
+ await main();
132
+ } catch (error) {
133
+ console.error(await formatCliError(error));
134
+ process.exit(1);
135
+ }
136
+ })();
124
137
  }
125
138
 
126
139
  export default createWpTypiaCli;
@@ -32,12 +32,16 @@ const STRING_OPTION_NAMES_BY_COMMAND = {
32
32
  "anchor",
33
33
  "block",
34
34
  "data-storage",
35
+ "external-layer-id",
36
+ "external-layer-source",
35
37
  "persistence-policy",
36
38
  "position",
37
39
  "template",
38
40
  ]),
39
41
  create: new Set([
40
42
  "data-storage",
43
+ "external-layer-id",
44
+ "external-layer-source",
41
45
  "namespace",
42
46
  "package-manager",
43
47
  "persistence-policy",
@@ -34,6 +34,14 @@ const addOptions = {
34
34
  description: "Persistence storage mode for persistence-capable templates.",
35
35
  schema: z.string().optional(),
36
36
  },
37
+ "external-layer-id": {
38
+ description: "Explicit layer id when an external layer package exposes multiple selectable layers.",
39
+ schema: z.string().optional(),
40
+ },
41
+ "external-layer-source": {
42
+ description: "Local path, GitHub locator, or npm package that exposes wp-typia.layers.json for built-in block templates.",
43
+ schema: z.string().optional(),
44
+ },
37
45
  "persistence-policy": {
38
46
  description: "Persistence write policy for persistence-capable templates.",
39
47
  schema: z.string().optional(),
@@ -49,6 +57,7 @@ const addOptions = {
49
57
  };
50
58
 
51
59
  export const addCommand = defineCommand({
60
+ defaultFormat: "toon",
52
61
  description: "Extend an official wp-typia workspace with blocks, variations, patterns, binding sources, or hooked blocks.",
53
62
  handler: async (args) => {
54
63
  await executeAddCommand({
@@ -76,6 +85,12 @@ export const addCommand = defineCommand({
76
85
  "data-storage":
77
86
  (args.flags["data-storage"] as string | undefined) ??
78
87
  config["data-storage"],
88
+ "external-layer-id":
89
+ (args.flags["external-layer-id"] as string | undefined) ??
90
+ config["external-layer-id"],
91
+ "external-layer-source":
92
+ (args.flags["external-layer-source"] as string | undefined) ??
93
+ config["external-layer-source"],
79
94
  anchor: (args.flags.anchor as string | undefined) ?? "",
80
95
  block: (args.flags.block as string | undefined) ?? "",
81
96
  kind:
@@ -26,6 +26,14 @@ const createOptions = {
26
26
  description: "Persistence storage mode for persistence-capable templates.",
27
27
  schema: z.string().optional(),
28
28
  },
29
+ "external-layer-id": {
30
+ description: "Explicit layer id when an external layer package exposes multiple selectable layers.",
31
+ schema: z.string().optional(),
32
+ },
33
+ "external-layer-source": {
34
+ description: "Local path, GitHub locator, or npm package that exposes wp-typia.layers.json for built-in templates.",
35
+ schema: z.string().optional(),
36
+ },
29
37
  namespace: {
30
38
  description: "Override the default block namespace.",
31
39
  schema: z.string().optional(),
@@ -85,11 +93,16 @@ const createOptions = {
85
93
  };
86
94
 
87
95
  export const createCommand = defineCommand({
96
+ defaultFormat: "toon",
88
97
  description: "Scaffold a new wp-typia project.",
89
98
  handler: async (args) => {
90
99
  const projectDir = args.positional[0];
91
100
  if (!projectDir) {
92
- throw new Error("`wp-typia create` requires <project-dir>.");
101
+ const { createCliCommandError } = await import("@wp-typia/project-tools/cli-diagnostics");
102
+ throw createCliCommandError({
103
+ command: "create",
104
+ detailLines: ["`wp-typia create` requires <project-dir>."],
105
+ });
93
106
  }
94
107
  await executeCreateCommand({
95
108
  cwd: args.cwd,
@@ -115,6 +128,12 @@ export const createCommand = defineCommand({
115
128
  "data-storage":
116
129
  (args.flags["data-storage"] as string | undefined) ??
117
130
  config["data-storage"],
131
+ "external-layer-id":
132
+ (args.flags["external-layer-id"] as string | undefined) ??
133
+ config["external-layer-id"],
134
+ "external-layer-source":
135
+ (args.flags["external-layer-source"] as string | undefined) ??
136
+ config["external-layer-source"],
118
137
  namespace:
119
138
  (args.flags.namespace as string | undefined) ?? config.namespace,
120
139
  "no-install": Boolean(
@@ -2,18 +2,26 @@ import { defineCommand } from "@bunli/core";
2
2
  import { executeDoctorCommand } from "../runtime-bridge";
3
3
 
4
4
  export const doctorCommand = defineCommand({
5
+ defaultFormat: "toon",
5
6
  description: "Run repository and project diagnostics.",
6
7
  handler: async (args) => {
7
8
  const prefersStructuredOutput =
8
9
  (args.formatExplicit && args.format !== "toon") ||
9
- args.agent ||
10
10
  Boolean(args.context?.store?.isAIAgent);
11
11
  if (prefersStructuredOutput) {
12
- const { getDoctorChecks } = await import("@wp-typia/project-tools/cli-doctor");
12
+ const [{ getDoctorChecks }, { createCliCommandError, getDoctorFailureDetailLines }] =
13
+ await Promise.all([
14
+ import("@wp-typia/project-tools/cli-doctor"),
15
+ import("@wp-typia/project-tools/cli-diagnostics"),
16
+ ]);
13
17
  const checks = await getDoctorChecks(args.cwd);
14
18
  args.output({ checks });
15
19
  if (checks.some((check) => check.status === "fail")) {
16
- throw new Error("Doctor found one or more failing checks.");
20
+ throw createCliCommandError({
21
+ command: "doctor",
22
+ detailLines: getDoctorFailureDetailLines(checks),
23
+ summary: "One or more doctor checks failed.",
24
+ });
17
25
  }
18
26
  return;
19
27
  }
@@ -58,6 +58,7 @@ const migrateOptions = {
58
58
  };
59
59
 
60
60
  export const migrateCommand = defineCommand({
61
+ defaultFormat: "toon",
61
62
  description: "Run migration workflows for migration-capable wp-typia projects.",
62
63
  handler: async (args) => {
63
64
  await executeMigrateCommand({
package/src/config.ts CHANGED
@@ -12,6 +12,8 @@ export type WpTypiaSchemaSource = {
12
12
  export type WpTypiaUserConfig = {
13
13
  create?: {
14
14
  "data-storage"?: string;
15
+ "external-layer-id"?: string;
16
+ "external-layer-source"?: string;
15
17
  namespace?: string;
16
18
  "no-install"?: boolean;
17
19
  "package-manager"?: string;
@@ -28,6 +30,8 @@ export type WpTypiaUserConfig = {
28
30
  add?: {
29
31
  block?: {
30
32
  "data-storage"?: string;
33
+ "external-layer-id"?: string;
34
+ "external-layer-source"?: string;
31
35
  "persistence-policy"?: string;
32
36
  template?: string;
33
37
  };
@@ -29,9 +29,12 @@ type AddExecutionInput = {
29
29
  cwd: string;
30
30
  emitOutput?: boolean;
31
31
  flags: Record<string, unknown>;
32
+ interactive?: boolean;
32
33
  kind?: string;
33
34
  name?: string;
34
35
  printLine?: PrintLine;
36
+ prompt?: ReadlinePrompt;
37
+ warnLine?: PrintLine;
35
38
  };
36
39
 
37
40
  type TemplatesExecutionInput = {
@@ -56,6 +59,12 @@ type MigrateExecutionInput = {
56
59
 
57
60
  type PrintLine = (line: string) => void;
58
61
  type PackageManagerId = "bun" | "npm" | "pnpm" | "yarn";
62
+ type CliCommandId = "add" | "create" | "doctor" | "migrate";
63
+ type ExternalLayerSelectOption = {
64
+ description?: string;
65
+ extends: string[];
66
+ id: string;
67
+ };
59
68
 
60
69
  type SyncProjectContext = {
61
70
  cwd: string;
@@ -65,18 +74,56 @@ type SyncProjectContext = {
65
74
  };
66
75
 
67
76
  const loadCliAddRuntime = () => import("@wp-typia/project-tools/cli-add");
77
+ const loadCliDiagnosticsRuntime = () => import("@wp-typia/project-tools/cli-diagnostics");
68
78
  const loadCliDoctorRuntime = () => import("@wp-typia/project-tools/cli-doctor");
69
79
  const loadCliPromptRuntime = () => import("@wp-typia/project-tools/cli-prompt");
70
80
  const loadCliScaffoldRuntime = () => import("@wp-typia/project-tools/cli-scaffold");
71
81
  const loadCliTemplatesRuntime = () => import("@wp-typia/project-tools/cli-templates");
72
82
  const loadMigrationsRuntime = () => import("@wp-typia/project-tools/migrations");
73
83
 
84
+ async function wrapCliCommandError(command: CliCommandId, error: unknown) {
85
+ const { createCliCommandError } = await loadCliDiagnosticsRuntime();
86
+ return createCliCommandError({ command, error });
87
+ }
88
+
89
+ function shouldWrapCliCommandError(options: {
90
+ emitOutput?: boolean;
91
+ renderLine?: ((line: string) => void) | undefined;
92
+ }): boolean {
93
+ if (options.emitOutput === false) {
94
+ return false;
95
+ }
96
+
97
+ if (options.renderLine) {
98
+ return false;
99
+ }
100
+
101
+ return true;
102
+ }
103
+
74
104
  function printBlock(lines: string[], printLine: PrintLine): void {
75
105
  for (const line of lines) {
76
106
  printLine(line);
77
107
  }
78
108
  }
79
109
 
110
+ function formatExternalLayerSelectHint(option: ExternalLayerSelectOption): string | undefined {
111
+ const details = [
112
+ option.description,
113
+ option.extends.length > 0 ? `extends ${option.extends.join(", ")}` : undefined,
114
+ ].filter((value): value is string => typeof value === "string" && value.length > 0);
115
+
116
+ return details.length > 0 ? details.join(" · ") : undefined;
117
+ }
118
+
119
+ function toExternalLayerPromptOptions(options: ExternalLayerSelectOption[]) {
120
+ return options.map((option) => ({
121
+ hint: formatExternalLayerSelectHint(option),
122
+ label: option.id,
123
+ value: option.id,
124
+ }));
125
+ }
126
+
80
127
  export function printCompletionPayload(
81
128
  payload: AlternateBufferCompletionPayload,
82
129
  options: {
@@ -172,6 +219,7 @@ function buildAddCompletionPayload(options: {
172
219
  kind: "binding-source" | "block" | "hooked-block" | "pattern" | "variation";
173
220
  projectDir: string;
174
221
  values: Record<string, string>;
222
+ warnings?: string[];
175
223
  }): AlternateBufferCompletionPayload {
176
224
  switch (options.kind) {
177
225
  case "variation":
@@ -218,6 +266,7 @@ function buildAddCompletionPayload(options: {
218
266
  `Project directory: ${options.projectDir}`,
219
267
  ],
220
268
  title: "✅ Added workspace block",
269
+ warningLines: options.warnings,
221
270
  };
222
271
  }
223
272
  }
@@ -431,11 +480,15 @@ export async function executeCreateCommand({
431
480
  const shouldPrompt =
432
481
  interactive ?? (!Boolean(flags.yes) && Boolean(process.stdin.isTTY) && Boolean(process.stdout.isTTY));
433
482
  const activePrompt = shouldPrompt ? (prompt ?? createReadlinePrompt()) : undefined;
483
+ const shouldPromptForExternalLayerSelection =
484
+ Boolean(activePrompt) && activePrompt !== prompt;
434
485
 
435
486
  try {
436
487
  const flow = await runScaffoldFlow({
437
488
  cwd,
438
489
  dataStorageMode: readOptionalLooseStringFlag(flags, "data-storage"),
490
+ externalLayerId: readOptionalLooseStringFlag(flags, "external-layer-id"),
491
+ externalLayerSource: readOptionalLooseStringFlag(flags, "external-layer-source"),
439
492
  isInteractive: Boolean(activePrompt),
440
493
  namespace: readOptionalLooseStringFlag(flags, "namespace"),
441
494
  noInstall: Boolean(flags["no-install"]),
@@ -449,6 +502,14 @@ export async function executeCreateCommand({
449
502
  selectDataStorage: activePrompt
450
503
  ? () => activePrompt.select("Select a data storage mode", [...DATA_STORAGE_PROMPT_OPTIONS], 1)
451
504
  : undefined,
505
+ selectExternalLayerId: shouldPromptForExternalLayerSelection && activePrompt
506
+ ? (options) =>
507
+ activePrompt.select(
508
+ "Select an external layer",
509
+ toExternalLayerPromptOptions(options),
510
+ 1,
511
+ )
512
+ : undefined,
452
513
  selectPackageManager: activePrompt
453
514
  ? () => activePrompt.select("Select a package manager", [...PACKAGE_MANAGER_PROMPT_OPTIONS], 1)
454
515
  : undefined,
@@ -495,6 +556,11 @@ export async function executeCreateCommand({
495
556
  });
496
557
  }
497
558
  return payload;
559
+ } catch (error) {
560
+ if (!shouldWrapCliCommandError({ emitOutput })) {
561
+ throw error;
562
+ }
563
+ throw await wrapCliCommandError("create", error);
498
564
  } finally {
499
565
  if (activePrompt && activePrompt !== prompt) {
500
566
  activePrompt.close();
@@ -506,9 +572,12 @@ export async function executeAddCommand({
506
572
  cwd,
507
573
  emitOutput = true,
508
574
  flags,
575
+ interactive,
509
576
  kind,
510
577
  name,
511
578
  printLine = console.log as PrintLine,
579
+ prompt,
580
+ warnLine = console.warn as PrintLine,
512
581
  }: AddExecutionInput): Promise<AlternateBufferCompletionPayload | void> {
513
582
  if (!kind) {
514
583
  const { formatAddHelpText } = await loadCliAddRuntime();
@@ -517,168 +586,205 @@ export async function executeAddCommand({
517
586
  }
518
587
 
519
588
  const addRuntime = await loadCliAddRuntime();
589
+ let activePrompt: ReadlinePrompt | undefined;
520
590
 
521
- if (kind === "variation") {
522
- if (!name) {
523
- throw new Error(
524
- "`wp-typia add variation` requires <name>. Usage: wp-typia add variation <name> --block <block-slug>",
525
- );
591
+ try {
592
+ if (kind === "variation") {
593
+ if (!name) {
594
+ throw new Error(
595
+ "`wp-typia add variation` requires <name>. Usage: wp-typia add variation <name> --block <block-slug>",
596
+ );
597
+ }
598
+
599
+ const blockSlug = readOptionalStringFlag(flags, "block");
600
+ if (!blockSlug) {
601
+ throw new Error("`wp-typia add variation` requires --block <block-slug>.");
602
+ }
603
+
604
+ const result = await addRuntime.runAddVariationCommand({
605
+ blockName: blockSlug,
606
+ cwd,
607
+ variationName: name,
608
+ });
609
+ const payload = buildAddCompletionPayload({
610
+ kind: "variation",
611
+ projectDir: result.projectDir,
612
+ values: {
613
+ blockSlug: result.blockSlug,
614
+ variationSlug: result.variationSlug,
615
+ },
616
+ });
617
+ if (emitOutput) {
618
+ printCompletionPayload(payload, { printLine });
619
+ }
620
+ return payload;
526
621
  }
527
622
 
528
- const blockSlug = readOptionalStringFlag(flags, "block");
529
- if (!blockSlug) {
530
- throw new Error("`wp-typia add variation` requires --block <block-slug>.");
531
- }
623
+ if (kind === "pattern") {
624
+ if (!name) {
625
+ throw new Error(
626
+ "`wp-typia add pattern` requires <name>. Usage: wp-typia add pattern <name>.",
627
+ );
628
+ }
532
629
 
533
- const result = await addRuntime.runAddVariationCommand({
534
- blockName: blockSlug,
535
- cwd,
536
- variationName: name,
537
- });
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 });
630
+ const result = await addRuntime.runAddPatternCommand({
631
+ cwd,
632
+ patternName: name,
633
+ });
634
+ const payload = buildAddCompletionPayload({
635
+ kind: "pattern",
636
+ projectDir: result.projectDir,
637
+ values: {
638
+ patternSlug: result.patternSlug,
639
+ },
640
+ });
641
+ if (emitOutput) {
642
+ printCompletionPayload(payload, { printLine });
643
+ }
644
+ return payload;
548
645
  }
549
- return payload;
550
- }
551
646
 
552
- if (kind === "pattern") {
553
- if (!name) {
554
- throw new Error(
555
- "`wp-typia add pattern` requires <name>. Usage: wp-typia add pattern <name>.",
556
- );
647
+ if (kind === "binding-source") {
648
+ if (!name) {
649
+ throw new Error(
650
+ "`wp-typia add binding-source` requires <name>. Usage: wp-typia add binding-source <name>.",
651
+ );
652
+ }
653
+
654
+ const result = await addRuntime.runAddBindingSourceCommand({
655
+ bindingSourceName: name,
656
+ cwd,
657
+ });
658
+ const payload = buildAddCompletionPayload({
659
+ kind: "binding-source",
660
+ projectDir: result.projectDir,
661
+ values: {
662
+ bindingSourceSlug: result.bindingSourceSlug,
663
+ },
664
+ });
665
+ if (emitOutput) {
666
+ printCompletionPayload(payload, { printLine });
667
+ }
668
+ return payload;
557
669
  }
558
670
 
559
- const result = await addRuntime.runAddPatternCommand({
560
- cwd,
561
- patternName: name,
562
- });
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 });
671
+ if (kind === "hooked-block") {
672
+ if (!name) {
673
+ throw new Error(
674
+ "`wp-typia add hooked-block` requires <block-slug>. Usage: wp-typia add hooked-block <block-slug> --anchor <anchor-block-name> --position <before|after|firstChild|lastChild>.",
675
+ );
676
+ }
677
+
678
+ const anchorBlockName = readOptionalStringFlag(flags, "anchor");
679
+ if (!anchorBlockName) {
680
+ throw new Error("`wp-typia add hooked-block` requires --anchor <anchor-block-name>.");
681
+ }
682
+
683
+ const position = readOptionalStringFlag(flags, "position");
684
+ if (!position) {
685
+ throw new Error(
686
+ "`wp-typia add hooked-block` requires --position <before|after|firstChild|lastChild>.",
687
+ );
688
+ }
689
+
690
+ const result = await addRuntime.runAddHookedBlockCommand({
691
+ anchorBlockName,
692
+ blockName: name,
693
+ cwd,
694
+ position,
695
+ });
696
+ const payload = buildAddCompletionPayload({
697
+ kind: "hooked-block",
698
+ projectDir: result.projectDir,
699
+ values: {
700
+ anchorBlockName: result.anchorBlockName,
701
+ blockSlug: result.blockSlug,
702
+ position: result.position,
703
+ },
704
+ });
705
+ if (emitOutput) {
706
+ printCompletionPayload(payload, { printLine });
707
+ }
708
+ return payload;
572
709
  }
573
- return payload;
574
- }
575
710
 
576
- if (kind === "binding-source") {
577
- if (!name) {
711
+ if (kind !== "block") {
578
712
  throw new Error(
579
- "`wp-typia add binding-source` requires <name>. Usage: wp-typia add binding-source <name>.",
713
+ `Unknown add kind "${kind}". Expected one of: block, variation, pattern, binding-source, hooked-block.`,
580
714
  );
581
715
  }
582
716
 
583
- const result = await addRuntime.runAddBindingSourceCommand({
584
- bindingSourceName: name,
585
- cwd,
586
- });
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;
598
- }
599
-
600
- if (kind === "hooked-block") {
601
717
  if (!name) {
602
718
  throw new Error(
603
- "`wp-typia add hooked-block` requires <block-slug>. Usage: wp-typia add hooked-block <block-slug> --anchor <anchor-block-name> --position <before|after|firstChild|lastChild>.",
719
+ "`wp-typia add block` requires <name>. Usage: wp-typia add block <name> --template <basic|interactivity|persistence|compound>",
604
720
  );
605
721
  }
606
722
 
607
- const anchorBlockName = readOptionalStringFlag(flags, "anchor");
608
- if (!anchorBlockName) {
609
- throw new Error("`wp-typia add hooked-block` requires --anchor <anchor-block-name>.");
610
- }
611
-
612
- const position = readOptionalStringFlag(flags, "position");
613
- if (!position) {
723
+ if (!flags.template) {
614
724
  throw new Error(
615
- "`wp-typia add hooked-block` requires --position <before|after|firstChild|lastChild>.",
725
+ "`wp-typia add block` requires --template <basic|interactivity|persistence|compound>.",
616
726
  );
617
727
  }
618
728
 
619
- const result = await addRuntime.runAddHookedBlockCommand({
620
- anchorBlockName,
729
+ const externalLayerId = readOptionalStringFlag(flags, "external-layer-id");
730
+ const externalLayerSource = readOptionalStringFlag(flags, "external-layer-source");
731
+ const shouldPromptForLayerSelection =
732
+ Boolean(externalLayerSource) &&
733
+ !Boolean(externalLayerId) &&
734
+ (interactive ?? (Boolean(process.stdin.isTTY) && Boolean(process.stdout.isTTY)));
735
+ const promptRuntime = shouldPromptForLayerSelection
736
+ ? await loadCliPromptRuntime()
737
+ : undefined;
738
+ activePrompt = shouldPromptForLayerSelection
739
+ ? (prompt ?? promptRuntime?.createReadlinePrompt())
740
+ : undefined;
741
+ const selectPrompt = activePrompt;
742
+
743
+ const result = await addRuntime.runAddBlockCommand({
621
744
  blockName: name,
622
745
  cwd,
623
- position,
746
+ dataStorageMode: readOptionalStringFlag(flags, "data-storage"),
747
+ externalLayerId,
748
+ externalLayerSource,
749
+ persistencePolicy: readOptionalStringFlag(flags, "persistence-policy"),
750
+ selectExternalLayerId: selectPrompt
751
+ ? (options) =>
752
+ selectPrompt.select(
753
+ "Select an external layer",
754
+ toExternalLayerPromptOptions(options),
755
+ 1,
756
+ )
757
+ : undefined,
758
+ templateId: readOptionalStringFlag(flags, "template") as
759
+ | "basic"
760
+ | "interactivity"
761
+ | "persistence"
762
+ | "compound",
624
763
  });
764
+
625
765
  const payload = buildAddCompletionPayload({
626
- kind: "hooked-block",
766
+ kind: "block",
627
767
  projectDir: result.projectDir,
628
768
  values: {
629
- anchorBlockName: result.anchorBlockName,
630
- blockSlug: result.blockSlug,
631
- position: result.position,
769
+ blockSlugs: result.blockSlugs.join(", "),
770
+ templateId: result.templateId,
632
771
  },
772
+ warnings: result.warnings,
633
773
  });
634
774
  if (emitOutput) {
635
- printCompletionPayload(payload, { printLine });
775
+ printCompletionPayload(payload, { printLine, warnLine });
636
776
  }
637
777
  return payload;
778
+ } catch (error) {
779
+ if (!shouldWrapCliCommandError({ emitOutput })) {
780
+ throw error;
781
+ }
782
+ throw await wrapCliCommandError("add", error);
783
+ } finally {
784
+ if (activePrompt && activePrompt !== prompt) {
785
+ activePrompt.close();
786
+ }
638
787
  }
639
-
640
- if (kind !== "block") {
641
- throw new Error(
642
- `Unknown add kind "${kind}". Expected one of: block, variation, pattern, binding-source, hooked-block.`,
643
- );
644
- }
645
-
646
- if (!name) {
647
- throw new Error(
648
- "`wp-typia add block` requires <name>. Usage: wp-typia add block <name> --template <basic|interactivity|persistence|compound>",
649
- );
650
- }
651
-
652
- if (!flags.template) {
653
- throw new Error(
654
- "`wp-typia add block` requires --template <basic|interactivity|persistence|compound>.",
655
- );
656
- }
657
-
658
- const result = await addRuntime.runAddBlockCommand({
659
- blockName: name,
660
- cwd,
661
- dataStorageMode: readOptionalStringFlag(flags, "data-storage"),
662
- persistencePolicy: readOptionalStringFlag(flags, "persistence-policy"),
663
- templateId: readOptionalStringFlag(flags, "template") as
664
- | "basic"
665
- | "interactivity"
666
- | "persistence"
667
- | "compound",
668
- });
669
-
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;
682
788
  }
683
789
 
684
790
  export async function executeTemplatesCommand(
@@ -720,8 +826,12 @@ export async function executeTemplatesCommand(
720
826
  }
721
827
 
722
828
  export async function executeDoctorCommand(cwd: string): Promise<void> {
723
- const { runDoctor } = await loadCliDoctorRuntime();
724
- await runDoctor(cwd);
829
+ try {
830
+ const { runDoctor } = await loadCliDoctorRuntime();
831
+ await runDoctor(cwd);
832
+ } catch (error) {
833
+ throw await wrapCliCommandError("doctor", error);
834
+ }
725
835
  }
726
836
 
727
837
  export async function loadAddWorkspaceBlockOptions(cwd: string) {
@@ -767,53 +877,60 @@ export async function executeMigrateCommand({
767
877
  return;
768
878
  }
769
879
 
770
- const argv = [command];
771
- pushFlag(argv, "all", flags.all);
772
- pushFlag(argv, "force", flags.force);
773
- pushFlag(
774
- argv,
775
- "current-migration-version",
776
- readOptionalLooseStringFlag(flags, "current-migration-version"),
777
- );
778
- pushFlag(argv, "migration-version", readOptionalLooseStringFlag(flags, "migration-version"));
779
- pushFlag(
780
- argv,
781
- "from-migration-version",
782
- readOptionalLooseStringFlag(flags, "from-migration-version"),
783
- );
784
- pushFlag(
785
- argv,
786
- "to-migration-version",
787
- readOptionalLooseStringFlag(flags, "to-migration-version"),
788
- );
789
- pushFlag(argv, "iterations", readOptionalLooseStringFlag(flags, "iterations"));
790
- pushFlag(argv, "seed", readOptionalLooseStringFlag(flags, "seed"));
791
-
792
- const parsed = parseMigrationArgs(argv);
793
- const lines: string[] | null = renderLine ? [] : null;
794
- const captureLine = (line: string) => {
795
- lines?.push(line);
880
+ try {
881
+ const argv = [command];
882
+ pushFlag(argv, "all", flags.all);
883
+ pushFlag(argv, "force", flags.force);
884
+ pushFlag(
885
+ argv,
886
+ "current-migration-version",
887
+ readOptionalLooseStringFlag(flags, "current-migration-version"),
888
+ );
889
+ pushFlag(argv, "migration-version", readOptionalLooseStringFlag(flags, "migration-version"));
890
+ pushFlag(
891
+ argv,
892
+ "from-migration-version",
893
+ readOptionalLooseStringFlag(flags, "from-migration-version"),
894
+ );
895
+ pushFlag(
896
+ argv,
897
+ "to-migration-version",
898
+ readOptionalLooseStringFlag(flags, "to-migration-version"),
899
+ );
900
+ pushFlag(argv, "iterations", readOptionalLooseStringFlag(flags, "iterations"));
901
+ pushFlag(argv, "seed", readOptionalLooseStringFlag(flags, "seed"));
902
+
903
+ const parsed = parseMigrationArgs(argv);
904
+ const lines: string[] | null = renderLine ? [] : null;
905
+ const captureLine = (line: string) => {
906
+ lines?.push(line);
907
+ if (renderLine) {
908
+ renderLine(line);
909
+ return;
910
+ }
911
+ console.log(line);
912
+ };
913
+ const result = await runMigrationCommand(parsed, cwd, {
914
+ prompt,
915
+ renderLine: captureLine,
916
+ });
796
917
  if (renderLine) {
797
- renderLine(line);
798
- return;
918
+ return result && typeof result === "object" && "cancelled" in result && result.cancelled === true
919
+ ? undefined
920
+ : buildMigrationCompletionPayload({
921
+ command: parsed.command ?? "plan",
922
+ lines: lines ?? [],
923
+ });
799
924
  }
800
- console.log(line);
801
- };
802
- const result = await runMigrationCommand(parsed, cwd, {
803
- prompt,
804
- renderLine: captureLine,
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
925
 
815
- if (result && typeof result === "object" && "cancelled" in result && result.cancelled === true) {
816
- return;
926
+ if (result && typeof result === "object" && "cancelled" in result && result.cancelled === true) {
927
+ return;
928
+ }
929
+ } catch (error) {
930
+ if (!shouldWrapCliCommandError({ renderLine })) {
931
+ throw error;
932
+ }
933
+ throw await wrapCliCommandError("migrate", error);
817
934
  }
818
935
  }
819
936
 
@@ -11,6 +11,8 @@ export const addFlowSchema = z.object({
11
11
  anchor: z.string().optional(),
12
12
  block: z.string().optional(),
13
13
  "data-storage": z.string().optional(),
14
+ "external-layer-id": z.string().optional(),
15
+ "external-layer-source": z.string().optional(),
14
16
  kind: z
15
17
  .enum(["block", "variation", "pattern", "binding-source", "hooked-block"])
16
18
  .default("block"),
@@ -116,5 +118,17 @@ export function sanitizeAddSubmitValues(values: AddFlowValues): Record<string, u
116
118
  }
117
119
  }
118
120
 
121
+ if ((values.kind ?? "block") === "block") {
122
+ for (const hiddenFieldName of ["external-layer-source", "external-layer-id"] as const) {
123
+ const value = values[hiddenFieldName];
124
+ if (typeof value === "string") {
125
+ const trimmed = value.trim();
126
+ if (trimmed.length > 0) {
127
+ sanitized[hiddenFieldName] = trimmed;
128
+ }
129
+ }
130
+ }
131
+ }
132
+
119
133
  return sanitized;
120
134
  }
@@ -310,6 +310,7 @@ export function AddFlow({ cwd, initialValues }: AddFlowProps) {
310
310
  cwd,
311
311
  emitOutput: false,
312
312
  flags,
313
+ interactive: false,
313
314
  kind: values.kind,
314
315
  name: typeof flags.name === "string" ? flags.name : undefined,
315
316
  });
@@ -10,6 +10,8 @@ import {
10
10
 
11
11
  export const createFlowSchema = z.object({
12
12
  "data-storage": z.string().optional(),
13
+ "external-layer-id": z.string().optional(),
14
+ "external-layer-source": z.string().optional(),
13
15
  namespace: z.string().optional(),
14
16
  "no-install": z.boolean().default(false),
15
17
  "package-manager": z.string().optional(),
@@ -82,6 +84,24 @@ export function isCreatePersistenceTemplate(template?: string): boolean {
82
84
  return template === "persistence" || template === "compound";
83
85
  }
84
86
 
87
+ function supportsCreateExternalLayers(template?: string): boolean {
88
+ return (
89
+ template === "basic" ||
90
+ template === "interactivity" ||
91
+ template === "persistence" ||
92
+ template === "compound"
93
+ );
94
+ }
95
+
96
+ function normalizeOptionalHiddenString(value?: string): string | undefined {
97
+ if (typeof value !== "string") {
98
+ return undefined;
99
+ }
100
+
101
+ const trimmed = value.trim();
102
+ return trimmed.length > 0 ? trimmed : undefined;
103
+ }
104
+
85
105
  export function getVisibleCreateFieldNames(
86
106
  values: Partial<CreateFlowValues>,
87
107
  ): Array<CreateFieldName> {
@@ -113,12 +133,22 @@ export function getCreateScrollTop(options: {
113
133
  }
114
134
 
115
135
  export function sanitizeCreateSubmitValues(values: CreateFlowValues): CreateFlowValues {
136
+ const normalizedValues: CreateFlowValues = {
137
+ ...values,
138
+ "external-layer-id": supportsCreateExternalLayers(values.template)
139
+ ? normalizeOptionalHiddenString(values["external-layer-id"])
140
+ : undefined,
141
+ "external-layer-source": supportsCreateExternalLayers(values.template)
142
+ ? normalizeOptionalHiddenString(values["external-layer-source"])
143
+ : undefined,
144
+ };
145
+
116
146
  if (isCreatePersistenceTemplate(values.template)) {
117
- return values;
147
+ return normalizedValues;
118
148
  }
119
149
 
120
150
  return {
121
- ...values,
151
+ ...normalizedValues,
122
152
  "data-storage": undefined,
123
153
  "persistence-policy": undefined,
124
154
  };