@wp-typia/project-tools 0.16.7 → 0.16.9

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.
Files changed (54) hide show
  1. package/README.md +26 -1
  2. package/dist/runtime/block-generator-service.d.ts +9 -1
  3. package/dist/runtime/block-generator-service.js +137 -16
  4. package/dist/runtime/block-generator-tool-contract.d.ts +93 -0
  5. package/dist/runtime/block-generator-tool-contract.js +157 -0
  6. package/dist/runtime/built-in-block-artifacts.js +171 -423
  7. package/dist/runtime/built-in-block-code-artifacts.js +96 -46
  8. package/dist/runtime/built-in-block-code-templates.d.ts +36 -0
  9. package/dist/runtime/built-in-block-code-templates.js +2233 -0
  10. package/dist/runtime/cli-add-block.d.ts +2 -1
  11. package/dist/runtime/cli-add-block.js +163 -25
  12. package/dist/runtime/cli-add-shared.d.ts +7 -0
  13. package/dist/runtime/cli-add-shared.js +4 -6
  14. package/dist/runtime/cli-add-workspace.js +56 -17
  15. package/dist/runtime/cli-core.d.ts +4 -0
  16. package/dist/runtime/cli-core.js +3 -0
  17. package/dist/runtime/cli-diagnostics.d.ts +58 -0
  18. package/dist/runtime/cli-diagnostics.js +101 -0
  19. package/dist/runtime/cli-doctor.d.ts +2 -1
  20. package/dist/runtime/cli-doctor.js +16 -5
  21. package/dist/runtime/cli-help.js +4 -4
  22. package/dist/runtime/cli-scaffold.d.ts +5 -1
  23. package/dist/runtime/cli-scaffold.js +138 -111
  24. package/dist/runtime/external-layer-selection.d.ts +14 -0
  25. package/dist/runtime/external-layer-selection.js +35 -0
  26. package/dist/runtime/index.d.ts +4 -2
  27. package/dist/runtime/index.js +2 -1
  28. package/dist/runtime/migration-render.d.ts +23 -1
  29. package/dist/runtime/migration-render.js +58 -10
  30. package/dist/runtime/migration-ui-capability.js +17 -8
  31. package/dist/runtime/migration-utils.d.ts +7 -6
  32. package/dist/runtime/migration-utils.js +76 -73
  33. package/dist/runtime/migrations.js +2 -2
  34. package/dist/runtime/object-utils.d.ts +8 -1
  35. package/dist/runtime/object-utils.js +21 -1
  36. package/dist/runtime/scaffold-apply-utils.d.ts +14 -2
  37. package/dist/runtime/scaffold-apply-utils.js +19 -6
  38. package/dist/runtime/scaffold-repository-reference.d.ts +22 -0
  39. package/dist/runtime/scaffold-repository-reference.js +119 -0
  40. package/dist/runtime/scaffold.d.ts +5 -1
  41. package/dist/runtime/scaffold.js +15 -37
  42. package/dist/runtime/template-builtins.d.ts +11 -1
  43. package/dist/runtime/template-builtins.js +118 -25
  44. package/dist/runtime/template-layers.d.ts +37 -0
  45. package/dist/runtime/template-layers.js +184 -0
  46. package/dist/runtime/template-render.d.ts +28 -2
  47. package/dist/runtime/template-render.js +122 -55
  48. package/dist/runtime/template-source.d.ts +23 -5
  49. package/dist/runtime/template-source.js +296 -217
  50. package/package.json +8 -3
  51. package/templates/_shared/base/src/validator-toolkit.ts.mustache +2 -2
  52. package/templates/_shared/compound/core/scripts/add-compound-child.ts.mustache +58 -12
  53. package/templates/_shared/migration-ui/common/src/migrations/helpers.ts +19 -47
  54. package/templates/_shared/migration-ui/common/src/migrations/index.ts +40 -11
@@ -0,0 +1,101 @@
1
+ const DEFAULT_CLI_FAILURE_SUMMARIES = {
2
+ add: "Unable to complete the requested add workflow.",
3
+ create: "Unable to complete the requested create workflow.",
4
+ doctor: "One or more doctor checks failed.",
5
+ migrate: "Unable to complete the requested migration command.",
6
+ };
7
+ function formatCliDiagnosticBlock(message) {
8
+ const lines = [`wp-typia ${message.command} failed`, `Summary: ${message.summary}`];
9
+ if (message.detailLines.length > 0) {
10
+ lines.push("Details:");
11
+ for (const detailLine of message.detailLines) {
12
+ lines.push(`- ${detailLine}`);
13
+ }
14
+ }
15
+ return lines.join("\n");
16
+ }
17
+ function getErrorMessage(error) {
18
+ if (error instanceof Error) {
19
+ return error.message;
20
+ }
21
+ return String(error);
22
+ }
23
+ function normalizeDetailLines(detailLines) {
24
+ return detailLines
25
+ .flatMap((detailLine) => typeof detailLine === "string" ? detailLine.split("\n") : [])
26
+ .map((detailLine) => detailLine.trim())
27
+ .filter((detailLine) => detailLine.length > 0);
28
+ }
29
+ /**
30
+ * Structured CLI failure carrying a stable summary/detail layout.
31
+ */
32
+ export class CliDiagnosticError extends Error {
33
+ constructor(message, options) {
34
+ super(formatCliDiagnosticBlock(message), options);
35
+ this.command = message.command;
36
+ this.detailLines = [...message.detailLines];
37
+ this.name = "CliDiagnosticError";
38
+ this.summary = message.summary;
39
+ }
40
+ }
41
+ /**
42
+ * Narrow an unknown error to the shared CLI diagnostic error shape.
43
+ */
44
+ export function isCliDiagnosticError(error) {
45
+ return error instanceof CliDiagnosticError;
46
+ }
47
+ /**
48
+ * Build a shared diagnostic error for one CLI command failure.
49
+ */
50
+ export function createCliCommandError(options) {
51
+ if (isCliDiagnosticError(options.error)) {
52
+ return options.error;
53
+ }
54
+ const summary = options.summary ?? DEFAULT_CLI_FAILURE_SUMMARIES[options.command] ?? "Command failed.";
55
+ const detailLines = normalizeDetailLines(options.detailLines ?? [options.error === undefined ? undefined : getErrorMessage(options.error)]);
56
+ return new CliDiagnosticError({
57
+ command: options.command,
58
+ detailLines,
59
+ summary,
60
+ }, options.error instanceof Error ? { cause: options.error } : undefined);
61
+ }
62
+ /**
63
+ * Render a CLI diagnostic block. Non-diagnostic errors fall back to their
64
+ * plain message so existing non-command failures keep working.
65
+ */
66
+ export function formatCliDiagnosticError(error) {
67
+ if (!isCliDiagnosticError(error)) {
68
+ return getErrorMessage(error);
69
+ }
70
+ return formatCliDiagnosticBlock({
71
+ command: error.command,
72
+ detailLines: error.detailLines,
73
+ summary: error.summary,
74
+ });
75
+ }
76
+ /**
77
+ * Format one human-readable doctor check row.
78
+ */
79
+ export function formatDoctorCheckLine(check) {
80
+ return `${check.status === "pass" ? "PASS" : "FAIL"} ${check.label}: ${check.detail}`;
81
+ }
82
+ /**
83
+ * Return the failing doctor checks from one doctor run.
84
+ */
85
+ export function getFailingDoctorChecks(checks) {
86
+ return checks.filter((check) => check.status === "fail");
87
+ }
88
+ /**
89
+ * Format the final doctor summary row.
90
+ */
91
+ export function formatDoctorSummaryLine(checks) {
92
+ const failedChecks = getFailingDoctorChecks(checks);
93
+ return `${failedChecks.length === 0 ? "PASS" : "FAIL"} wp-typia doctor summary: ${checks.length - failedChecks.length}/${checks.length} checks passed`;
94
+ }
95
+ /**
96
+ * Build detail lines for doctor failures so the non-interactive formatter can
97
+ * restate the failed checks after the streaming rows.
98
+ */
99
+ export function getDoctorFailureDetailLines(checks) {
100
+ return getFailingDoctorChecks(checks).map((check) => `${check.label}: ${check.detail}`);
101
+ }
@@ -11,6 +11,7 @@ export interface DoctorCheck {
11
11
  }
12
12
  interface RunDoctorOptions {
13
13
  renderLine?: (check: DoctorCheck) => void;
14
+ renderSummaryLine?: (summaryLine: string) => void;
14
15
  }
15
16
  /**
16
17
  * Collect all runtime doctor checks for the current environment.
@@ -30,5 +31,5 @@ export declare function getDoctorChecks(cwd: string): Promise<DoctorCheck[]>;
30
31
  * @returns The completed list of doctor checks.
31
32
  * @throws {Error} When one or more checks fail.
32
33
  */
33
- export declare function runDoctor(cwd: string, { renderLine, }?: RunDoctorOptions): Promise<DoctorCheck[]>;
34
+ export declare function runDoctor(cwd: string, options?: RunDoctorOptions): Promise<DoctorCheck[]>;
34
35
  export {};
@@ -3,11 +3,13 @@ import os from "node:os";
3
3
  import path from "node:path";
4
4
  import { execFileSync } from "node:child_process";
5
5
  import { access, constants as fsConstants, rm, writeFile } from "node:fs/promises";
6
+ import { parseScaffoldBlockMetadata } from "@wp-typia/block-runtime/blocks";
6
7
  import { getBuiltInTemplateLayerDirs, isOmittableBuiltInTemplateLayerDir, } from "./template-builtins.js";
7
8
  import { HOOKED_BLOCK_ANCHOR_PATTERN, HOOKED_BLOCK_POSITION_SET, } from "./hooked-blocks.js";
8
9
  import { isBuiltInTemplateId, listTemplates } from "./template-registry.js";
9
10
  import { readWorkspaceInventory } from "./workspace-inventory.js";
10
11
  import { getInvalidWorkspaceProjectReason, parseWorkspacePackageJson, WORKSPACE_TEMPLATE_PACKAGE, tryResolveWorkspaceProject, } from "./workspace-project.js";
12
+ import { createCliCommandError, formatDoctorCheckLine, formatDoctorSummaryLine, getDoctorFailureDetailLines, } from "./cli-diagnostics.js";
11
13
  const WORKSPACE_COLLECTION_IMPORT_LINE = "import '../../collection';";
12
14
  const WORKSPACE_COLLECTION_IMPORT_PATTERN = /^\s*import\s+["']\.\.\/\.\.\/collection["']\s*;?\s*$/m;
13
15
  const WORKSPACE_BINDING_SERVER_GLOB = "/src/bindings/*/server.php";
@@ -116,7 +118,7 @@ function checkWorkspaceBlockMetadata(projectDir, workspace, block) {
116
118
  }
117
119
  let blockJson;
118
120
  try {
119
- blockJson = JSON.parse(fs.readFileSync(blockJsonPath, "utf8"));
121
+ blockJson = parseScaffoldBlockMetadata(JSON.parse(fs.readFileSync(blockJsonPath, "utf8")));
120
122
  }
121
123
  catch (error) {
122
124
  return createDoctorCheck(`Block metadata ${block.slug}`, "fail", error instanceof Error ? error.message : String(error));
@@ -141,7 +143,7 @@ function checkWorkspaceBlockHooks(projectDir, blockSlug) {
141
143
  }
142
144
  let blockJson;
143
145
  try {
144
- blockJson = JSON.parse(fs.readFileSync(blockJsonPath, "utf8"));
146
+ blockJson = parseScaffoldBlockMetadata(JSON.parse(fs.readFileSync(blockJsonPath, "utf8")));
145
147
  }
146
148
  catch (error) {
147
149
  return createDoctorCheck(`Block hooks ${blockSlug}`, "fail", error instanceof Error ? error.message : String(error));
@@ -419,13 +421,22 @@ export async function getDoctorChecks(cwd) {
419
421
  * @returns The completed list of doctor checks.
420
422
  * @throws {Error} When one or more checks fail.
421
423
  */
422
- export async function runDoctor(cwd, { renderLine = (check) => console.log(`${check.status.toUpperCase()} ${check.label}: ${check.detail}`), } = {}) {
424
+ export async function runDoctor(cwd, options = {}) {
425
+ const renderLine = options.renderLine ?? ((check) => console.log(formatDoctorCheckLine(check)));
426
+ const renderSummaryLine = options.renderSummaryLine ??
427
+ (options.renderLine ? () => undefined : (summaryLine) => console.log(summaryLine));
423
428
  const checks = await getDoctorChecks(cwd);
424
429
  for (const check of checks) {
425
430
  renderLine(check);
426
431
  }
427
- if (checks.some((check) => check.status === "fail")) {
428
- throw new Error("Doctor found one or more failing checks.");
432
+ renderSummaryLine(formatDoctorSummaryLine(checks));
433
+ const failureDetailLines = getDoctorFailureDetailLines(checks);
434
+ if (failureDetailLines.length > 0) {
435
+ throw createCliCommandError({
436
+ command: "doctor",
437
+ detailLines: failureDetailLines,
438
+ summary: "One or more doctor checks failed.",
439
+ });
429
440
  }
430
441
  return checks;
431
442
  }
@@ -10,13 +10,13 @@ import { TEMPLATE_IDS } from "./template-registry.js";
10
10
  */
11
11
  export function formatHelpText() {
12
12
  return `Usage:
13
- wp-typia create <project-dir> [--template <basic|interactivity>] [--namespace <value>] [--text-domain <value>] [--php-prefix <value>] [--with-migration-ui] [--with-wp-env] [--with-test-preset] [--yes] [--no-install] [--package-manager <id>]
13
+ wp-typia create <project-dir> [--template <basic|interactivity>] [--external-layer-source <./path|github:owner/repo/path[#ref]|npm-package>] [--external-layer-id <layer-id>] [--namespace <value>] [--text-domain <value>] [--php-prefix <value>] [--with-migration-ui] [--with-wp-env] [--with-test-preset] [--yes] [--no-install] [--package-manager <id>]
14
14
  wp-typia create <project-dir> [--template <./path|github:owner/repo/path[#ref]>] [--variant <name>] [--namespace <value>] [--text-domain <value>] [--php-prefix <value>] [--with-wp-env] [--with-test-preset] [--yes] [--no-install] [--package-manager <id>]
15
15
  wp-typia create <project-dir> [--template <npm-package>] [--variant <name>] [--namespace <value>] [--text-domain <value>] [--php-prefix <value>] [--with-wp-env] [--with-test-preset] [--yes] [--no-install] [--package-manager <id>]
16
- wp-typia create <project-dir> [--template persistence] [--data-storage <post-meta|custom-table>] [--persistence-policy <authenticated|public>] [--namespace <value>] [--text-domain <value>] [--php-prefix <value>] [--with-migration-ui] [--with-wp-env] [--with-test-preset] [--yes] [--no-install] [--package-manager <id>]
17
- wp-typia create <project-dir> [--template compound] [--data-storage <post-meta|custom-table>] [--persistence-policy <authenticated|public>] [--namespace <value>] [--text-domain <value>] [--php-prefix <value>] [--with-migration-ui] [--with-wp-env] [--with-test-preset] [--yes] [--no-install] [--package-manager <id>]
16
+ wp-typia create <project-dir> [--template persistence] [--external-layer-source <./path|github:owner/repo/path[#ref]|npm-package>] [--external-layer-id <layer-id>] [--data-storage <post-meta|custom-table>] [--persistence-policy <authenticated|public>] [--namespace <value>] [--text-domain <value>] [--php-prefix <value>] [--with-migration-ui] [--with-wp-env] [--with-test-preset] [--yes] [--no-install] [--package-manager <id>]
17
+ wp-typia create <project-dir> [--template compound] [--external-layer-source <./path|github:owner/repo/path[#ref]|npm-package>] [--external-layer-id <layer-id>] [--data-storage <post-meta|custom-table>] [--persistence-policy <authenticated|public>] [--namespace <value>] [--text-domain <value>] [--php-prefix <value>] [--with-migration-ui] [--with-wp-env] [--with-test-preset] [--yes] [--no-install] [--package-manager <id>]
18
18
  wp-typia <project-dir> [create flags...]
19
- wp-typia add block <name> --template <basic|interactivity|persistence|compound> [--data-storage <post-meta|custom-table>] [--persistence-policy <authenticated|public>]
19
+ wp-typia add block <name> --template <basic|interactivity|persistence|compound> [--external-layer-source <./path|github:owner/repo/path[#ref]|npm-package>] [--external-layer-id <layer-id>] [--data-storage <post-meta|custom-table>] [--persistence-policy <authenticated|public>]
20
20
  wp-typia add variation <name> --block <block-slug>
21
21
  wp-typia add pattern <name>
22
22
  wp-typia add binding-source <name>
@@ -1,6 +1,7 @@
1
1
  import { collectScaffoldAnswers, scaffoldProject } from "./scaffold.js";
2
2
  import type { DataStorageMode, PersistencePolicy } from "./scaffold.js";
3
3
  import type { PackageManagerId } from "./package-managers.js";
4
+ import { type ExternalLayerSelectionOption } from "./external-layer-selection.js";
4
5
  import type { TemplateDefinition } from "./template-registry.js";
5
6
  interface GetNextStepsOptions {
6
7
  noInstall: boolean;
@@ -23,6 +24,8 @@ interface RunScaffoldFlowOptions {
23
24
  allowExistingDir?: boolean;
24
25
  cwd?: string;
25
26
  dataStorageMode?: string;
27
+ externalLayerId?: string;
28
+ externalLayerSource?: string;
26
29
  installDependencies?: Parameters<typeof scaffoldProject>[0]["installDependencies"];
27
30
  isInteractive?: boolean;
28
31
  namespace?: string;
@@ -32,6 +35,7 @@ interface RunScaffoldFlowOptions {
32
35
  projectInput: string;
33
36
  promptText?: Parameters<typeof collectScaffoldAnswers>[0]["promptText"];
34
37
  selectDataStorage?: () => Promise<DataStorageMode>;
38
+ selectExternalLayerId?: (options: ExternalLayerSelectionOption[]) => Promise<string>;
35
39
  selectPackageManager?: () => Promise<PackageManagerId>;
36
40
  selectPersistencePolicy?: () => Promise<PersistencePolicy>;
37
41
  selectTemplate?: () => Promise<TemplateDefinition["id"]>;
@@ -69,7 +73,7 @@ export declare function getOptionalOnboarding({ availableScripts, packageManager
69
73
  * project.
70
74
  * @returns The scaffold result together with next-step guidance.
71
75
  */
72
- export declare function runScaffoldFlow({ projectInput, cwd, templateId, dataStorageMode, persistencePolicy, packageManager, namespace, textDomain, phpPrefix, yes, noInstall, isInteractive, allowExistingDir, selectTemplate, selectDataStorage, selectPersistencePolicy, selectPackageManager, promptText, installDependencies, variant, selectWithTestPreset, selectWithWpEnv, selectWithMigrationUi, withMigrationUi, withTestPreset, withWpEnv, }: RunScaffoldFlowOptions): Promise<{
76
+ export declare function runScaffoldFlow({ projectInput, cwd, templateId, dataStorageMode, externalLayerId, externalLayerSource, persistencePolicy, packageManager, namespace, textDomain, phpPrefix, yes, noInstall, isInteractive, allowExistingDir, selectTemplate, selectDataStorage, selectExternalLayerId, selectPersistencePolicy, selectPackageManager, promptText, installDependencies, variant, selectWithTestPreset, selectWithWpEnv, selectWithMigrationUi, withMigrationUi, withTestPreset, withWpEnv, }: RunScaffoldFlowOptions): Promise<{
73
77
  optionalOnboarding: OptionalOnboardingGuidance;
74
78
  projectDir: string;
75
79
  projectInput: string;
@@ -5,6 +5,7 @@ import { formatInstallCommand, formatRunScript, } from "./package-managers.js";
5
5
  import { getPrimaryDevelopmentScript } from "./local-dev-presets.js";
6
6
  import { getOptionalOnboardingNote, getOptionalOnboardingSteps, } from "./scaffold-onboarding.js";
7
7
  import { OFFICIAL_WORKSPACE_TEMPLATE_PACKAGE, isBuiltInTemplateId, } from "./template-registry.js";
8
+ import { resolveOptionalInteractiveExternalLayerId, } from "./external-layer-selection.js";
8
9
  function templateUsesPersistenceSettings(templateId, options) {
9
10
  if (templateId === "persistence") {
10
11
  return true;
@@ -98,7 +99,14 @@ export function getOptionalOnboarding({ availableScripts, packageManager, templa
98
99
  * project.
99
100
  * @returns The scaffold result together with next-step guidance.
100
101
  */
101
- export async function runScaffoldFlow({ projectInput, cwd = process.cwd(), templateId, dataStorageMode, persistencePolicy, packageManager, namespace, textDomain, phpPrefix, yes = false, noInstall = false, isInteractive = false, allowExistingDir = false, selectTemplate, selectDataStorage, selectPersistencePolicy, selectPackageManager, promptText, installDependencies = undefined, variant, selectWithTestPreset, selectWithWpEnv, selectWithMigrationUi, withMigrationUi, withTestPreset, withWpEnv, }) {
102
+ export async function runScaffoldFlow({ projectInput, cwd = process.cwd(), templateId, dataStorageMode, externalLayerId, externalLayerSource, persistencePolicy, packageManager, namespace, textDomain, phpPrefix, yes = false, noInstall = false, isInteractive = false, allowExistingDir = false, selectTemplate, selectDataStorage, selectExternalLayerId, selectPersistencePolicy, selectPackageManager, promptText, installDependencies = undefined, variant, selectWithTestPreset, selectWithWpEnv, selectWithMigrationUi, withMigrationUi, withTestPreset, withWpEnv, }) {
103
+ const normalizedExternalLayerId = typeof externalLayerId === "string" && externalLayerId.trim().length > 0
104
+ ? externalLayerId.trim()
105
+ : undefined;
106
+ const normalizedExternalLayerSource = typeof externalLayerSource === "string" &&
107
+ externalLayerSource.trim().length > 0
108
+ ? externalLayerSource.trim()
109
+ : undefined;
102
110
  if (!projectInput) {
103
111
  throw new Error("Project directory is required. Usage: wp-typia create <project-dir> (or wp-typia <project-dir>).");
104
112
  }
@@ -108,119 +116,138 @@ export async function runScaffoldFlow({ projectInput, cwd = process.cwd(), templ
108
116
  isInteractive,
109
117
  selectTemplate,
110
118
  });
111
- const shouldResolvePersistence = templateUsesPersistenceSettings(resolvedTemplateId, {
112
- dataStorageMode,
113
- persistencePolicy,
114
- });
115
- const resolvedDataStorage = await resolveOptionalSelection({
116
- allowedValues: DATA_STORAGE_MODES,
117
- defaultValue: "custom-table",
118
- explicitValue: dataStorageMode,
119
- isInteractive,
120
- isValue: isDataStorageMode,
121
- label: "data storage mode",
122
- select: selectDataStorage,
123
- shouldResolve: shouldResolvePersistence,
124
- yes,
125
- });
126
- const resolvedPersistencePolicy = await resolveOptionalSelection({
127
- allowedValues: PERSISTENCE_POLICIES,
128
- defaultValue: "authenticated",
129
- explicitValue: persistencePolicy,
130
- isInteractive,
131
- isValue: isPersistencePolicy,
132
- label: "persistence policy",
133
- select: selectPersistencePolicy,
134
- shouldResolve: shouldResolvePersistence,
135
- yes,
136
- });
137
- const resolvedPackageManager = await resolvePackageManagerId({
138
- packageManager,
139
- yes,
140
- isInteractive,
141
- selectPackageManager,
142
- });
143
- const resolvedWithWpEnv = await resolveOptionalBooleanFlag({
144
- explicitValue: withWpEnv,
145
- isInteractive,
146
- select: selectWithWpEnv,
147
- yes,
148
- });
149
- const resolvedWithTestPreset = await resolveOptionalBooleanFlag({
150
- explicitValue: withTestPreset,
151
- isInteractive,
152
- select: selectWithTestPreset,
153
- yes,
154
- });
155
- const resolvedWithMigrationUi = await resolveOptionalBooleanFlag({
156
- disabled: !isBuiltInTemplateId(resolvedTemplateId) &&
157
- resolvedTemplateId !== OFFICIAL_WORKSPACE_TEMPLATE_PACKAGE,
158
- explicitValue: withMigrationUi,
159
- isInteractive,
160
- select: selectWithMigrationUi,
161
- yes,
162
- });
163
- const projectDir = path.resolve(cwd, projectInput);
164
- const projectName = path.basename(projectDir);
165
- const answers = await collectScaffoldAnswers({
166
- dataStorageMode: resolvedDataStorage,
167
- namespace,
168
- persistencePolicy: resolvedPersistencePolicy,
169
- phpPrefix,
170
- projectName,
171
- templateId: resolvedTemplateId,
172
- textDomain,
173
- yes,
174
- promptText,
175
- });
176
- const result = await scaffoldProject({
177
- answers,
178
- allowExistingDir,
179
- cwd,
180
- dataStorageMode: resolvedDataStorage,
181
- installDependencies,
182
- noInstall,
183
- packageManager: resolvedPackageManager,
184
- persistencePolicy: resolvedPersistencePolicy,
185
- projectDir,
186
- templateId: resolvedTemplateId,
187
- variant,
188
- withMigrationUi: resolvedWithMigrationUi,
189
- withTestPreset: resolvedWithTestPreset,
190
- withWpEnv: resolvedWithWpEnv,
191
- });
192
- let availableScripts;
119
+ const resolvedExternalLayerSelection = isBuiltInTemplateId(resolvedTemplateId) && isInteractive
120
+ ? await resolveOptionalInteractiveExternalLayerId({
121
+ callerCwd: cwd,
122
+ externalLayerId: normalizedExternalLayerId,
123
+ externalLayerSource: normalizedExternalLayerSource,
124
+ selectExternalLayerId,
125
+ })
126
+ : {
127
+ externalLayerId: normalizedExternalLayerId,
128
+ externalLayerSource: normalizedExternalLayerSource,
129
+ };
193
130
  try {
194
- const parsedPackageJson = JSON.parse(fs.readFileSync(path.join(projectDir, "package.json"), "utf8"));
195
- const scripts = parsedPackageJson.scripts &&
196
- typeof parsedPackageJson.scripts === "object" &&
197
- !Array.isArray(parsedPackageJson.scripts)
198
- ? parsedPackageJson.scripts
199
- : {};
200
- availableScripts = Object.entries(scripts)
201
- .filter(([, value]) => typeof value === "string")
202
- .map(([scriptName]) => scriptName);
203
- }
204
- catch {
205
- availableScripts = undefined;
206
- }
207
- return {
208
- optionalOnboarding: getOptionalOnboarding({
209
- availableScripts,
131
+ const shouldResolvePersistence = templateUsesPersistenceSettings(resolvedTemplateId, {
132
+ dataStorageMode,
133
+ persistencePolicy,
134
+ });
135
+ const resolvedDataStorage = await resolveOptionalSelection({
136
+ allowedValues: DATA_STORAGE_MODES,
137
+ defaultValue: "custom-table",
138
+ explicitValue: dataStorageMode,
139
+ isInteractive,
140
+ isValue: isDataStorageMode,
141
+ label: "data storage mode",
142
+ select: selectDataStorage,
143
+ shouldResolve: shouldResolvePersistence,
144
+ yes,
145
+ });
146
+ const resolvedPersistencePolicy = await resolveOptionalSelection({
147
+ allowedValues: PERSISTENCE_POLICIES,
148
+ defaultValue: "authenticated",
149
+ explicitValue: persistencePolicy,
150
+ isInteractive,
151
+ isValue: isPersistencePolicy,
152
+ label: "persistence policy",
153
+ select: selectPersistencePolicy,
154
+ shouldResolve: shouldResolvePersistence,
155
+ yes,
156
+ });
157
+ const resolvedPackageManager = await resolvePackageManagerId({
158
+ packageManager,
159
+ yes,
160
+ isInteractive,
161
+ selectPackageManager,
162
+ });
163
+ const resolvedWithWpEnv = await resolveOptionalBooleanFlag({
164
+ explicitValue: withWpEnv,
165
+ isInteractive,
166
+ select: selectWithWpEnv,
167
+ yes,
168
+ });
169
+ const resolvedWithTestPreset = await resolveOptionalBooleanFlag({
170
+ explicitValue: withTestPreset,
171
+ isInteractive,
172
+ select: selectWithTestPreset,
173
+ yes,
174
+ });
175
+ const resolvedWithMigrationUi = await resolveOptionalBooleanFlag({
176
+ disabled: !isBuiltInTemplateId(resolvedTemplateId) &&
177
+ resolvedTemplateId !== OFFICIAL_WORKSPACE_TEMPLATE_PACKAGE,
178
+ explicitValue: withMigrationUi,
179
+ isInteractive,
180
+ select: selectWithMigrationUi,
181
+ yes,
182
+ });
183
+ const projectDir = path.resolve(cwd, projectInput);
184
+ const projectName = path.basename(projectDir);
185
+ const answers = await collectScaffoldAnswers({
186
+ dataStorageMode: resolvedDataStorage,
187
+ namespace,
188
+ persistencePolicy: resolvedPersistencePolicy,
189
+ phpPrefix,
190
+ projectName,
191
+ templateId: resolvedTemplateId,
192
+ textDomain,
193
+ yes,
194
+ promptText,
195
+ });
196
+ const result = await scaffoldProject({
197
+ answers,
198
+ allowExistingDir,
199
+ cwd,
200
+ dataStorageMode: resolvedDataStorage,
201
+ externalLayerId: resolvedExternalLayerSelection.externalLayerId,
202
+ externalLayerSource: resolvedExternalLayerSelection.externalLayerSource,
203
+ externalLayerSourceLabel: normalizedExternalLayerSource,
204
+ installDependencies,
205
+ noInstall,
210
206
  packageManager: resolvedPackageManager,
207
+ persistencePolicy: resolvedPersistencePolicy,
208
+ projectDir,
211
209
  templateId: resolvedTemplateId,
212
- compoundPersistenceEnabled: result.variables.compoundPersistenceEnabled === "true",
213
- }),
214
- projectDir,
215
- projectInput,
216
- packageManager: resolvedPackageManager,
217
- result,
218
- nextSteps: getNextSteps({
219
- projectInput,
210
+ variant,
211
+ withMigrationUi: resolvedWithMigrationUi,
212
+ withTestPreset: resolvedWithTestPreset,
213
+ withWpEnv: resolvedWithWpEnv,
214
+ });
215
+ let availableScripts;
216
+ try {
217
+ const parsedPackageJson = JSON.parse(fs.readFileSync(path.join(projectDir, "package.json"), "utf8"));
218
+ const scripts = parsedPackageJson.scripts &&
219
+ typeof parsedPackageJson.scripts === "object" &&
220
+ !Array.isArray(parsedPackageJson.scripts)
221
+ ? parsedPackageJson.scripts
222
+ : {};
223
+ availableScripts = Object.entries(scripts)
224
+ .filter(([, value]) => typeof value === "string")
225
+ .map(([scriptName]) => scriptName);
226
+ }
227
+ catch {
228
+ availableScripts = undefined;
229
+ }
230
+ return {
231
+ optionalOnboarding: getOptionalOnboarding({
232
+ availableScripts,
233
+ packageManager: resolvedPackageManager,
234
+ templateId: resolvedTemplateId,
235
+ compoundPersistenceEnabled: result.variables.compoundPersistenceEnabled === "true",
236
+ }),
220
237
  projectDir,
238
+ projectInput,
221
239
  packageManager: resolvedPackageManager,
222
- noInstall,
223
- templateId: resolvedTemplateId,
224
- }),
225
- };
240
+ result,
241
+ nextSteps: getNextSteps({
242
+ projectInput,
243
+ projectDir,
244
+ packageManager: resolvedPackageManager,
245
+ noInstall,
246
+ templateId: resolvedTemplateId,
247
+ }),
248
+ };
249
+ }
250
+ finally {
251
+ await resolvedExternalLayerSelection.cleanup?.();
252
+ }
226
253
  }
@@ -0,0 +1,14 @@
1
+ import { type SelectableExternalTemplateLayer } from "./template-layers.js";
2
+ export interface ExternalLayerSelectionOption extends SelectableExternalTemplateLayer {
3
+ }
4
+ export interface ResolvedExternalLayerSelection {
5
+ cleanup?: () => Promise<void>;
6
+ externalLayerId?: string;
7
+ externalLayerSource?: string;
8
+ }
9
+ export declare function resolveOptionalInteractiveExternalLayerId({ callerCwd, externalLayerId, externalLayerSource, selectExternalLayerId, }: {
10
+ callerCwd: string;
11
+ externalLayerId?: string;
12
+ externalLayerSource?: string;
13
+ selectExternalLayerId?: (options: ExternalLayerSelectionOption[]) => Promise<string>;
14
+ }): Promise<ResolvedExternalLayerSelection>;
@@ -0,0 +1,35 @@
1
+ import { parseTemplateLocator, resolveTemplateSeed, } from "./template-source.js";
2
+ import { listSelectableExternalTemplateLayers, } from "./template-layers.js";
3
+ export async function resolveOptionalInteractiveExternalLayerId({ callerCwd, externalLayerId, externalLayerSource, selectExternalLayerId, }) {
4
+ if (!externalLayerSource || externalLayerId || !selectExternalLayerId) {
5
+ return {
6
+ externalLayerId,
7
+ externalLayerSource,
8
+ };
9
+ }
10
+ const layerSeed = await resolveTemplateSeed(parseTemplateLocator(externalLayerSource), callerCwd);
11
+ try {
12
+ const selectableLayers = await listSelectableExternalTemplateLayers(layerSeed.rootDir);
13
+ if (selectableLayers.length <= 1) {
14
+ await layerSeed.cleanup?.();
15
+ return {
16
+ externalLayerId,
17
+ externalLayerSource,
18
+ };
19
+ }
20
+ const selectedLayerId = await selectExternalLayerId(selectableLayers);
21
+ if (selectableLayers.some((layer) => layer.id === selectedLayerId)) {
22
+ return {
23
+ cleanup: layerSeed.cleanup,
24
+ externalLayerId: selectedLayerId,
25
+ externalLayerSource: layerSeed.rootDir,
26
+ };
27
+ }
28
+ await layerSeed.cleanup?.();
29
+ throw new Error(`Unknown external layer "${selectedLayerId}". Expected one of: ${selectableLayers.map((layer) => layer.id).join(", ")}`);
30
+ }
31
+ catch (error) {
32
+ await layerSeed.cleanup?.();
33
+ throw error;
34
+ }
35
+ }
@@ -12,6 +12,8 @@
12
12
  export { scaffoldProject, collectScaffoldAnswers, getDefaultAnswers, getTemplateVariables, resolvePackageManagerId, resolveTemplateId, } from "./scaffold.js";
13
13
  export { BlockGeneratorService } from "./block-generator-service.js";
14
14
  export type { ApplyBlockInput, BlockGenerationTarget, BlockSpec, PlanBlockInput, PlanBlockResult, RenderBlockInput, RenderBlockResult, ValidateBlockInput, ValidateBlockResult, } from "./block-generator-service.js";
15
+ export { BLOCK_GENERATION_TOOL_CONTRACT_VERSION, inspectBlockGeneration, } from "./block-generator-tool-contract.js";
16
+ export type { BlockGenerationEmittedFilePreview, BlockGenerationRenderPreview, BlockGenerationStarterManifestPreview, BlockGenerationTemplateCopyPreview, BlockGenerationToolStage, InspectBlockGenerationInput, InspectBlockGenerationPlanResult, InspectBlockGenerationRenderResult, InspectBlockGenerationResult, InspectBlockGenerationValidateResult, } from "./block-generator-tool-contract.js";
15
17
  export { formatMigrationHelpText, parseMigrationArgs, runMigrationCommand, } from "./migrations.js";
16
18
  export { parseWorkspacePackageManagerId, resolveWorkspaceProject, tryResolveWorkspaceProject, } from "./workspace-project.js";
17
19
  export { manifestAttributeToJsonSchema, projectJsonSchemaDocument, manifestToJsonSchema, manifestToOpenApi, normalizeEndpointAuthDefinition, } from "./schema-core.js";
@@ -19,5 +21,5 @@ export { buildCompoundChildStarterManifestDocument, getStarterManifestFiles, str
19
21
  export type { EndpointAuthIntent, EndpointOpenApiAuthMode, EndpointOpenApiContractDocument, EndpointOpenApiDocumentOptions, EndpointOpenApiEndpointDefinition, EndpointOpenApiMethod, EndpointWordPressAuthDefinition, EndpointWordPressAuthMechanism, JsonSchemaDocument, JsonSchemaProjectionProfile, JsonSchemaObject, NormalizedEndpointAuthDefinition, OpenApiDocument, OpenApiInfo, OpenApiOperation, OpenApiParameter, OpenApiPathItem, OpenApiSecurityScheme, } from "./schema-core.js";
20
22
  export { PACKAGE_MANAGER_IDS, PACKAGE_MANAGERS, formatPackageExecCommand, formatInstallCommand, formatRunScript, getPackageManager, getPackageManagerSelectOptions, transformPackageManagerText, } from "./package-managers.js";
21
23
  export { TEMPLATE_IDS, TEMPLATE_REGISTRY, getTemplateById, getTemplateSelectOptions, listTemplates, } from "./template-registry.js";
22
- export { createReadlinePrompt, formatAddHelpText, formatHelpText, formatTemplateDetails, formatTemplateFeatures, formatTemplateSummary, getDoctorChecks, getNextSteps, getOptionalOnboarding, getWorkspaceBlockSelectOptions, HOOKED_BLOCK_POSITION_IDS, runAddBindingSourceCommand, runAddBlockCommand, runAddHookedBlockCommand, runAddPatternCommand, runDoctor, runAddVariationCommand, runScaffoldFlow, } from "./cli-core.js";
23
- export type { DoctorCheck, HookedBlockPositionId, ReadlinePrompt } from "./cli-core.js";
24
+ export { createReadlinePrompt, createCliCommandError, CliDiagnosticError, formatCliDiagnosticError, formatAddHelpText, formatDoctorCheckLine, formatDoctorSummaryLine, formatHelpText, formatTemplateDetails, formatTemplateFeatures, formatTemplateSummary, getDoctorChecks, getDoctorFailureDetailLines, getFailingDoctorChecks, getNextSteps, getOptionalOnboarding, getWorkspaceBlockSelectOptions, HOOKED_BLOCK_POSITION_IDS, isCliDiagnosticError, runAddBindingSourceCommand, runAddBlockCommand, runAddHookedBlockCommand, runAddPatternCommand, runDoctor, runAddVariationCommand, runScaffoldFlow, } from "./cli-core.js";
25
+ export type { CliDiagnosticMessage, DoctorCheck, HookedBlockPositionId, ReadlinePrompt, } from "./cli-core.js";
@@ -11,10 +11,11 @@
11
11
  */
12
12
  export { scaffoldProject, collectScaffoldAnswers, getDefaultAnswers, getTemplateVariables, resolvePackageManagerId, resolveTemplateId, } from "./scaffold.js";
13
13
  export { BlockGeneratorService } from "./block-generator-service.js";
14
+ export { BLOCK_GENERATION_TOOL_CONTRACT_VERSION, inspectBlockGeneration, } from "./block-generator-tool-contract.js";
14
15
  export { formatMigrationHelpText, parseMigrationArgs, runMigrationCommand, } from "./migrations.js";
15
16
  export { parseWorkspacePackageManagerId, resolveWorkspaceProject, tryResolveWorkspaceProject, } from "./workspace-project.js";
16
17
  export { manifestAttributeToJsonSchema, projectJsonSchemaDocument, manifestToJsonSchema, manifestToOpenApi, normalizeEndpointAuthDefinition, } from "./schema-core.js";
17
18
  export { buildCompoundChildStarterManifestDocument, getStarterManifestFiles, stringifyStarterManifest, } from "./starter-manifests.js";
18
19
  export { PACKAGE_MANAGER_IDS, PACKAGE_MANAGERS, formatPackageExecCommand, formatInstallCommand, formatRunScript, getPackageManager, getPackageManagerSelectOptions, transformPackageManagerText, } from "./package-managers.js";
19
20
  export { TEMPLATE_IDS, TEMPLATE_REGISTRY, getTemplateById, getTemplateSelectOptions, listTemplates, } from "./template-registry.js";
20
- export { createReadlinePrompt, formatAddHelpText, formatHelpText, formatTemplateDetails, formatTemplateFeatures, formatTemplateSummary, getDoctorChecks, getNextSteps, getOptionalOnboarding, getWorkspaceBlockSelectOptions, HOOKED_BLOCK_POSITION_IDS, runAddBindingSourceCommand, runAddBlockCommand, runAddHookedBlockCommand, runAddPatternCommand, runDoctor, runAddVariationCommand, runScaffoldFlow, } from "./cli-core.js";
21
+ export { createReadlinePrompt, createCliCommandError, CliDiagnosticError, formatCliDiagnosticError, formatAddHelpText, formatDoctorCheckLine, formatDoctorSummaryLine, formatHelpText, formatTemplateDetails, formatTemplateFeatures, formatTemplateSummary, getDoctorChecks, getDoctorFailureDetailLines, getFailingDoctorChecks, getNextSteps, getOptionalOnboarding, getWorkspaceBlockSelectOptions, HOOKED_BLOCK_POSITION_IDS, isCliDiagnosticError, runAddBindingSourceCommand, runAddBlockCommand, runAddHookedBlockCommand, runAddPatternCommand, runDoctor, runAddVariationCommand, runScaffoldFlow, } from "./cli-core.js";
@@ -3,8 +3,30 @@ export declare function formatDiffReport(diff: MigrationDiff, { includeRiskSumma
3
3
  includeRiskSummary?: boolean;
4
4
  }): string;
5
5
  export declare function renderMigrationRuleFile({ block, currentAttributes, currentTypeName, diff, fromVersion, projectDir, rulePath, targetVersion, }: MigrationRuleFileInput): string;
6
+ /**
7
+ * Renders the generated migration registry module for a block target.
8
+ *
9
+ * Prefers manifest wrapper modules when they are available in the project,
10
+ * while still validating the imported manifest before the registry consumes it.
11
+ *
12
+ * @param state The resolved migration project state.
13
+ * @param blockKey The stable key for the block whose registry is being generated.
14
+ * @param entries The generated migration entries to include in the registry.
15
+ * @returns The generated TypeScript source code for the migration registry file.
16
+ */
6
17
  export declare function renderMigrationRegistryFile(state: MigrationProjectState, blockKey: string, entries: GeneratedMigrationEntry[]): string;
7
- export declare function renderGeneratedDeprecatedFile(entries: MigrationEntry[]): string;
18
+ /**
19
+ * Renders the generated deprecated module for a block target.
20
+ *
21
+ * The emitted module exposes the ordered deprecation array consumed by block
22
+ * registration and migration helpers.
23
+ *
24
+ * @param state The resolved migration project state.
25
+ * @param blockKey The stable key for the block whose deprecated entries are being generated.
26
+ * @param entries The migration entries that define deprecated manifest versions.
27
+ * @returns The generated TypeScript source code for the deprecated module.
28
+ */
29
+ export declare function renderGeneratedDeprecatedFile(state: MigrationProjectState, blockKey: string, entries: MigrationEntry[]): string;
8
30
  export declare function renderGeneratedMigrationIndexFile(state: MigrationProjectState, entries: MigrationEntry[]): string;
9
31
  export declare function renderPhpMigrationRegistryFile(state: MigrationProjectState, entries: MigrationEntry[]): string;
10
32
  export declare function renderVerifyFile(state: MigrationProjectState, blockKey: string, entries: MigrationEntry[]): string;