@wp-typia/project-tools 0.19.0 → 0.19.2

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 (39) hide show
  1. package/dist/runtime/block-generator-service-spec.js +206 -2
  2. package/dist/runtime/built-in-block-artifacts.js +3 -1
  3. package/dist/runtime/built-in-block-code-artifacts.js +3 -1
  4. package/dist/runtime/built-in-block-non-ts-artifacts.js +1 -1039
  5. package/dist/runtime/built-in-block-non-ts-family-artifacts.d.ts +30 -0
  6. package/dist/runtime/built-in-block-non-ts-family-artifacts.js +1035 -0
  7. package/dist/runtime/built-in-block-non-ts-render-utils.d.ts +27 -0
  8. package/dist/runtime/built-in-block-non-ts-render-utils.js +51 -0
  9. package/dist/runtime/cli-add-block.js +11 -5
  10. package/dist/runtime/cli-add-workspace-rest-anchors.d.ts +3 -0
  11. package/dist/runtime/cli-add-workspace-rest-anchors.js +188 -0
  12. package/dist/runtime/cli-add-workspace-rest-source-emitters.d.ts +7 -0
  13. package/dist/runtime/cli-add-workspace-rest-source-emitters.js +379 -0
  14. package/dist/runtime/cli-add-workspace-rest.js +5 -564
  15. package/dist/runtime/cli-diagnostics.d.ts +26 -0
  16. package/dist/runtime/cli-diagnostics.js +107 -0
  17. package/dist/runtime/cli-doctor-workspace.js +4 -4
  18. package/dist/runtime/cli-scaffold.js +3 -3
  19. package/dist/runtime/index.d.ts +3 -1
  20. package/dist/runtime/index.js +2 -1
  21. package/dist/runtime/scaffold-bootstrap.js +5 -1
  22. package/dist/runtime/scaffold-documents.js +11 -8
  23. package/dist/runtime/scaffold-template-variable-groups.d.ts +154 -0
  24. package/dist/runtime/scaffold-template-variable-groups.js +13 -0
  25. package/dist/runtime/scaffold-template-variables.js +58 -1
  26. package/dist/runtime/scaffold.d.ts +5 -1
  27. package/dist/runtime/scaffold.js +5 -1
  28. package/dist/runtime/temp-roots.d.ts +44 -0
  29. package/dist/runtime/temp-roots.js +129 -0
  30. package/dist/runtime/template-builtins.js +4 -6
  31. package/dist/runtime/template-registry.d.ts +8 -0
  32. package/dist/runtime/template-registry.js +34 -1
  33. package/dist/runtime/template-source-external.d.ts +1 -0
  34. package/dist/runtime/template-source-external.js +4 -7
  35. package/dist/runtime/template-source-remote.js +44 -23
  36. package/dist/runtime/template-source-seeds.js +3 -9
  37. package/dist/runtime/template-source.d.ts +2 -3
  38. package/dist/runtime/template-source.js +8 -2
  39. package/package.json +6 -1
@@ -1,8 +1,23 @@
1
+ export const CLI_DIAGNOSTIC_CODES = {
2
+ COMMAND_EXECUTION: "command-execution",
3
+ CONFIGURATION_MISSING: "configuration-missing",
4
+ DEPENDENCIES_NOT_INSTALLED: "dependencies-not-installed",
5
+ DOCTOR_CHECK_FAILED: "doctor-check-failed",
6
+ INVALID_ARGUMENT: "invalid-argument",
7
+ INVALID_COMMAND: "invalid-command",
8
+ MISSING_ARGUMENT: "missing-argument",
9
+ MISSING_BUILD_ARTIFACT: "missing-build-artifact",
10
+ OUTSIDE_PROJECT_ROOT: "outside-project-root",
11
+ UNSUPPORTED_COMMAND: "unsupported-command",
12
+ };
1
13
  const DEFAULT_CLI_FAILURE_SUMMARIES = {
2
14
  add: "Unable to complete the requested add workflow.",
3
15
  create: "Unable to complete the requested create workflow.",
4
16
  doctor: "One or more doctor checks failed.",
17
+ mcp: "Unable to inspect or sync MCP metadata.",
5
18
  migrate: "Unable to complete the requested migration command.",
19
+ sync: "Unable to complete the requested sync workflow.",
20
+ templates: "Unable to inspect scaffold templates.",
6
21
  };
7
22
  const MIN_CLI_WRAP_COLUMNS = 32;
8
23
  function parseCliColumns(value) {
@@ -104,6 +119,7 @@ function normalizeDetailLines(detailLines) {
104
119
  export class CliDiagnosticError extends Error {
105
120
  constructor(message, options) {
106
121
  super(formatCliDiagnosticBlock(message), options);
122
+ this.code = message.code;
107
123
  this.command = message.command;
108
124
  this.detailLines = [...message.detailLines];
109
125
  this.name = "CliDiagnosticError";
@@ -116,6 +132,59 @@ export class CliDiagnosticError extends Error {
116
132
  export function isCliDiagnosticError(error) {
117
133
  return error instanceof CliDiagnosticError;
118
134
  }
135
+ function isCliDiagnosticCode(value) {
136
+ return Object.values(CLI_DIAGNOSTIC_CODES).includes(value);
137
+ }
138
+ function readCliDiagnosticCode(error) {
139
+ if (isCliDiagnosticError(error)) {
140
+ return error.code;
141
+ }
142
+ if (typeof error === "object" && error !== null && "code" in error) {
143
+ const { code } = error;
144
+ if (isCliDiagnosticCode(code)) {
145
+ return code;
146
+ }
147
+ }
148
+ return null;
149
+ }
150
+ function inferCliDiagnosticCode(options) {
151
+ const inheritedCode = readCliDiagnosticCode(options.error);
152
+ if (inheritedCode) {
153
+ return inheritedCode;
154
+ }
155
+ const haystack = normalizeDetailLines([
156
+ ...options.detailLines,
157
+ options.error === undefined ? undefined : getErrorMessage(options.error),
158
+ ]).join("\n");
159
+ if (/No MCP schema sources are configured\./u.test(haystack)) {
160
+ return CLI_DIAGNOSTIC_CODES.CONFIGURATION_MISSING;
161
+ }
162
+ if (/Missing bundled build artifacts/u.test(haystack)) {
163
+ return CLI_DIAGNOSTIC_CODES.MISSING_BUILD_ARTIFACT;
164
+ }
165
+ if (/No generated wp-typia project root was found/u.test(haystack)) {
166
+ return CLI_DIAGNOSTIC_CODES.OUTSIDE_PROJECT_ROOT;
167
+ }
168
+ if (/dependencies have not been installed yet/u.test(haystack)) {
169
+ return CLI_DIAGNOSTIC_CODES.DEPENDENCIES_NOT_INSTALLED;
170
+ }
171
+ if (options.command === "doctor") {
172
+ return CLI_DIAGNOSTIC_CODES.DOCTOR_CHECK_FAILED;
173
+ }
174
+ if (/requires <|requires --/u.test(haystack)) {
175
+ return CLI_DIAGNOSTIC_CODES.MISSING_ARGUMENT;
176
+ }
177
+ if (/Unknown .*subcommand|Unknown add kind|Unknown template|removed in favor|does not support|The Bun-free fallback runtime does not support|The positional alias only accepts/u.test(haystack)) {
178
+ return haystack.includes("does not support") ||
179
+ haystack.includes("The Bun-free fallback runtime does not support")
180
+ ? CLI_DIAGNOSTIC_CODES.UNSUPPORTED_COMMAND
181
+ : CLI_DIAGNOSTIC_CODES.INVALID_COMMAND;
182
+ }
183
+ if (/Invalid |must start with|cannot hook|cannot nest|cannot use|cannot be|already defines|already exists|Expected one of/u.test(haystack)) {
184
+ return CLI_DIAGNOSTIC_CODES.INVALID_ARGUMENT;
185
+ }
186
+ return CLI_DIAGNOSTIC_CODES.COMMAND_EXECUTION;
187
+ }
119
188
  /**
120
189
  * Build a shared diagnostic error for one CLI command failure.
121
190
  */
@@ -125,7 +194,14 @@ export function createCliCommandError(options) {
125
194
  }
126
195
  const summary = options.summary ?? DEFAULT_CLI_FAILURE_SUMMARIES[options.command] ?? "Command failed.";
127
196
  const detailLines = normalizeDetailLines(options.detailLines ?? [options.error === undefined ? undefined : getErrorMessage(options.error)]);
197
+ const code = options.code ??
198
+ inferCliDiagnosticCode({
199
+ command: options.command,
200
+ detailLines,
201
+ error: options.error,
202
+ });
128
203
  return new CliDiagnosticError({
204
+ code,
129
205
  command: options.command,
130
206
  detailLines,
131
207
  summary,
@@ -140,11 +216,42 @@ export function formatCliDiagnosticError(error) {
140
216
  return getErrorMessage(error);
141
217
  }
142
218
  return formatCliDiagnosticBlock({
219
+ code: error.code,
143
220
  command: error.command,
144
221
  detailLines: error.detailLines,
145
222
  summary: error.summary,
146
223
  });
147
224
  }
225
+ export function serializeCliDiagnosticError(error) {
226
+ if (isCliDiagnosticError(error)) {
227
+ return {
228
+ code: error.code,
229
+ command: error.command,
230
+ detailLines: [...error.detailLines],
231
+ kind: "command-execution",
232
+ message: formatCliDiagnosticBlock({
233
+ code: error.code,
234
+ command: error.command,
235
+ detailLines: error.detailLines,
236
+ summary: error.summary,
237
+ }),
238
+ name: error.name,
239
+ summary: error.summary,
240
+ tag: "CommandExecutionError",
241
+ };
242
+ }
243
+ return {
244
+ code: inferCliDiagnosticCode({
245
+ command: "unknown",
246
+ detailLines: [],
247
+ error,
248
+ }),
249
+ kind: "command-execution",
250
+ message: getErrorMessage(error),
251
+ name: error instanceof Error ? error.name : "Error",
252
+ tag: "CommandExecutionError",
253
+ };
254
+ }
148
255
  /**
149
256
  * Format one human-readable doctor check row.
150
257
  */
@@ -351,21 +351,21 @@ export function getWorkspaceDoctorChecks(cwd) {
351
351
  workspace = tryResolveWorkspaceProject(cwd);
352
352
  }
353
353
  catch (error) {
354
- checks.push(createDoctorScopeCheck("fail", "Environment checks ran, but workspace discovery could not continue. Fix the nearby workspace package metadata and rerun `wp-typia doctor`."));
354
+ checks.push(createDoctorScopeCheck("fail", "Scope: blocked before workspace checks. Environment checks ran, but workspace discovery could not continue. Fix the nearby workspace package metadata and rerun `wp-typia doctor`."));
355
355
  checks.push(createDoctorCheck("Workspace package metadata", "fail", error instanceof Error ? error.message : String(error)));
356
356
  return checks;
357
357
  }
358
358
  if (!workspace) {
359
359
  if (invalidWorkspaceReason) {
360
- checks.push(createDoctorScopeCheck("fail", "Environment checks ran, but workspace diagnostics could not continue because a nearby wp-typia workspace candidate is invalid. Fix the workspace package metadata and rerun `wp-typia doctor`."));
360
+ checks.push(createDoctorScopeCheck("fail", "Scope: blocked before workspace checks. Environment checks ran, but workspace diagnostics could not continue because a nearby wp-typia workspace candidate is invalid. Fix the workspace package metadata and rerun `wp-typia doctor`."));
361
361
  checks.push(createDoctorCheck("Workspace package metadata", "fail", invalidWorkspaceReason));
362
362
  }
363
363
  else {
364
- checks.push(createDoctorScopeCheck("pass", "No official wp-typia workspace root was detected, so this run only covered environment readiness. Re-run `wp-typia doctor` from a workspace root if you expected package metadata, inventory, or generated artifact checks."));
364
+ checks.push(createDoctorScopeCheck("pass", "Scope: environment-only. No official wp-typia workspace root was detected, so this run only covered environment readiness. Re-run `wp-typia doctor` from a workspace root if you expected package metadata, inventory, or generated artifact checks."));
365
365
  }
366
366
  return checks;
367
367
  }
368
- checks.push(createDoctorScopeCheck("pass", `Official workspace detected for ${workspace.workspace.namespace}; environment readiness checks ran and workspace-scoped diagnostics are enabled for the package metadata, inventory, source-tree drift, and any configured migration hint rows below.`));
368
+ checks.push(createDoctorScopeCheck("pass", `Scope: full workspace diagnostics for ${workspace.workspace.namespace}. Environment readiness checks ran and workspace-scoped diagnostics are enabled for the package metadata, inventory, source-tree drift, and any configured migration hint rows below.`));
369
369
  let workspacePackageJson;
370
370
  try {
371
371
  workspacePackageJson = parseWorkspacePackageJson(workspace.projectDir);
@@ -1,12 +1,12 @@
1
1
  import fs from "node:fs";
2
2
  import { promises as fsp } from "node:fs";
3
- import os from "node:os";
4
3
  import path from "node:path";
5
4
  import { collectScaffoldAnswers, DATA_STORAGE_MODES, PERSISTENCE_POLICIES, isDataStorageMode, isPersistencePolicy, resolvePackageManagerId, resolveTemplateId, scaffoldProject, } from "./scaffold.js";
6
5
  import { parseAlternateRenderTargets } from "./alternate-render-targets.js";
7
6
  import { parseCompoundInnerBlocksPreset } from "./compound-inner-blocks.js";
8
7
  import { formatInstallCommand, formatRunScript, } from "./package-managers.js";
9
8
  import { getPrimaryDevelopmentScript } from "./local-dev-presets.js";
9
+ import { createManagedTempRoot } from "./temp-roots.js";
10
10
  import { getOptionalOnboardingNote, getOptionalOnboardingSteps, } from "./scaffold-onboarding.js";
11
11
  import { formatNonEmptyTargetDirectoryError } from "./scaffold-bootstrap.js";
12
12
  import { OFFICIAL_WORKSPACE_TEMPLATE_PACKAGE, isBuiltInTemplateId, } from "./template-registry.js";
@@ -41,7 +41,7 @@ async function assertDryRunTargetDirectoryReady(projectDir, allowExistingDir) {
41
41
  }
42
42
  async function buildScaffoldDryRunPlan({ allowExistingDir, alternateRenderTargets, answers, cwd, dataStorageMode, externalLayerId, externalLayerSource, externalLayerSourceLabel, installDependencies, noInstall, onProgress, packageManager, persistencePolicy, projectDir, templateId, variant, withMigrationUi, withTestPreset, withWpEnv, }) {
43
43
  await assertDryRunTargetDirectoryReady(projectDir, allowExistingDir);
44
- const tempRoot = await fsp.mkdtemp(path.join(os.tmpdir(), "wp-typia-scaffold-plan-"));
44
+ const { path: tempRoot, cleanup } = await createManagedTempRoot("wp-typia-scaffold-plan-");
45
45
  const previewProjectDir = path.join(tempRoot, "preview-project");
46
46
  try {
47
47
  const result = await scaffoldProject({
@@ -75,7 +75,7 @@ async function buildScaffoldDryRunPlan({ allowExistingDir, alternateRenderTarget
75
75
  };
76
76
  }
77
77
  finally {
78
- await fsp.rm(tempRoot, { force: true, recursive: true });
78
+ await cleanup();
79
79
  }
80
80
  }
81
81
  function validateCreateProjectInput(projectInput) {
@@ -12,8 +12,9 @@
12
12
  */
13
13
  export { COMPOUND_INNER_BLOCKS_PRESET_IDS, DEFAULT_COMPOUND_INNER_BLOCKS_PRESET_ID, getCompoundInnerBlocksPresetDefinition, isCompoundInnerBlocksPresetId, parseCompoundInnerBlocksPreset, resolveCompoundInnerBlocksPreset, } from "./compound-inner-blocks.js";
14
14
  export type { CompoundInnerBlocksPresetDefinition, CompoundInnerBlocksPresetId, CompoundInnerBlocksTemplateLock, } from "./compound-inner-blocks.js";
15
- export { scaffoldProject, collectScaffoldAnswers, getDefaultAnswers, getTemplateVariables, resolvePackageManagerId, resolveTemplateId, } from "./scaffold.js";
15
+ export { scaffoldProject, collectScaffoldAnswers, getScaffoldTemplateVariableGroups, getDefaultAnswers, getTemplateVariables, resolvePackageManagerId, resolveTemplateId, } from "./scaffold.js";
16
16
  export { BlockGeneratorService } from "./block-generator-service.js";
17
+ export type { BasicScaffoldTemplateVariableGroups, CompoundScaffoldTemplateVariableGroups, ExternalScaffoldTemplateVariableGroups, FlatScaffoldTemplateVariables, InteractivityScaffoldTemplateVariableGroups, PersistenceScaffoldTemplateVariableGroups, QueryLoopScaffoldTemplateVariableGroups, ScaffoldTemplateFamily, ScaffoldTemplateVariableGroups, } from "./scaffold.js";
17
18
  export type { ApplyBlockInput, BlockGenerationTarget, BlockSpec, PlanBlockInput, PlanBlockResult, RenderBlockInput, RenderBlockResult, ValidateBlockInput, ValidateBlockResult, } from "./block-generator-service.js";
18
19
  export { BLOCK_GENERATION_TOOL_CONTRACT_VERSION, inspectBlockGeneration, } from "./block-generator-tool-contract.js";
19
20
  export type { BlockGenerationEmittedFilePreview, BlockGenerationRenderPreview, BlockGenerationStarterManifestPreview, BlockGenerationTemplateCopyPreview, BlockGenerationToolStage, InspectBlockGenerationInput, InspectBlockGenerationPlanResult, InspectBlockGenerationRenderResult, InspectBlockGenerationResult, InspectBlockGenerationValidateResult, } from "./block-generator-tool-contract.js";
@@ -24,5 +25,6 @@ export { buildCompoundChildStarterManifestDocument, getStarterManifestFiles, str
24
25
  export type { EndpointAuthIntent, EndpointOpenApiAuthMode, EndpointOpenApiContractDocument, EndpointOpenApiDocumentOptions, EndpointOpenApiEndpointDefinition, EndpointOpenApiMethod, EndpointWordPressAuthDefinition, EndpointWordPressAuthMechanism, JsonSchemaDocument, JsonSchemaProjectionProfile, JsonSchemaObject, NormalizedEndpointAuthDefinition, OpenApiDocument, OpenApiInfo, OpenApiOperation, OpenApiParameter, OpenApiPathItem, OpenApiSecurityScheme, } from "./schema-core.js";
25
26
  export { PACKAGE_MANAGER_IDS, PACKAGE_MANAGERS, formatPackageExecCommand, formatInstallCommand, formatRunScript, getPackageManager, getPackageManagerSelectOptions, transformPackageManagerText, } from "./package-managers.js";
26
27
  export { TEMPLATE_IDS, TEMPLATE_REGISTRY, getTemplateById, getTemplateSelectOptions, listTemplates, } from "./template-registry.js";
28
+ export { STALE_TEMP_ROOT_MAX_AGE_MS, WP_TYPIA_TEMP_ROOT_PREFIX, cleanupManagedTempRoot, cleanupStaleTempRoots, createManagedTempRoot, getTrackedTempRoots, } from "./temp-roots.js";
27
29
  export { createReadlinePrompt, createCliCommandError, CliDiagnosticError, formatCliDiagnosticError, formatAddHelpText, formatDoctorCheckLine, formatDoctorSummaryLine, formatHelpText, formatTemplateDetails, formatTemplateFeatures, formatTemplateSummary, getDoctorChecks, getDoctorFailureDetailLines, getFailingDoctorChecks, getNextSteps, getOptionalOnboarding, getWorkspaceBlockSelectOptions, HOOKED_BLOCK_POSITION_IDS, EDITOR_PLUGIN_SLOT_IDS, isCliDiagnosticError, runAddBindingSourceCommand, runAddBlockCommand, runAddEditorPluginCommand, runAddHookedBlockCommand, runAddPatternCommand, runDoctor, runAddVariationCommand, runScaffoldFlow, } from "./cli-core.js";
28
30
  export type { CliDiagnosticMessage, DoctorCheck, EditorPluginSlotId, HookedBlockPositionId, ReadlinePrompt, } from "./cli-core.js";
@@ -11,7 +11,7 @@
11
11
  * `HOOKED_BLOCK_POSITION_IDS`, and `runDoctor`.
12
12
  */
13
13
  export { COMPOUND_INNER_BLOCKS_PRESET_IDS, DEFAULT_COMPOUND_INNER_BLOCKS_PRESET_ID, getCompoundInnerBlocksPresetDefinition, isCompoundInnerBlocksPresetId, parseCompoundInnerBlocksPreset, resolveCompoundInnerBlocksPreset, } from "./compound-inner-blocks.js";
14
- export { scaffoldProject, collectScaffoldAnswers, getDefaultAnswers, getTemplateVariables, resolvePackageManagerId, resolveTemplateId, } from "./scaffold.js";
14
+ export { scaffoldProject, collectScaffoldAnswers, getScaffoldTemplateVariableGroups, getDefaultAnswers, getTemplateVariables, resolvePackageManagerId, resolveTemplateId, } from "./scaffold.js";
15
15
  export { BlockGeneratorService } from "./block-generator-service.js";
16
16
  export { BLOCK_GENERATION_TOOL_CONTRACT_VERSION, inspectBlockGeneration, } from "./block-generator-tool-contract.js";
17
17
  export { formatMigrationHelpText, parseMigrationArgs, runMigrationCommand, } from "./migrations.js";
@@ -20,4 +20,5 @@ export { manifestAttributeToJsonSchema, projectJsonSchemaDocument, manifestToJso
20
20
  export { buildCompoundChildStarterManifestDocument, getStarterManifestFiles, stringifyStarterManifest, } from "./starter-manifests.js";
21
21
  export { PACKAGE_MANAGER_IDS, PACKAGE_MANAGERS, formatPackageExecCommand, formatInstallCommand, formatRunScript, getPackageManager, getPackageManagerSelectOptions, transformPackageManagerText, } from "./package-managers.js";
22
22
  export { TEMPLATE_IDS, TEMPLATE_REGISTRY, getTemplateById, getTemplateSelectOptions, listTemplates, } from "./template-registry.js";
23
+ export { STALE_TEMP_ROOT_MAX_AGE_MS, WP_TYPIA_TEMP_ROOT_PREFIX, cleanupManagedTempRoot, cleanupStaleTempRoots, createManagedTempRoot, getTrackedTempRoots, } from "./temp-roots.js";
23
24
  export { createReadlinePrompt, createCliCommandError, CliDiagnosticError, formatCliDiagnosticError, formatAddHelpText, formatDoctorCheckLine, formatDoctorSummaryLine, formatHelpText, formatTemplateDetails, formatTemplateFeatures, formatTemplateSummary, getDoctorChecks, getDoctorFailureDetailLines, getFailingDoctorChecks, getNextSteps, getOptionalOnboarding, getWorkspaceBlockSelectOptions, HOOKED_BLOCK_POSITION_IDS, EDITOR_PLUGIN_SLOT_IDS, isCliDiagnosticError, runAddBindingSourceCommand, runAddBlockCommand, runAddEditorPluginCommand, runAddHookedBlockCommand, runAddPatternCommand, runDoctor, runAddVariationCommand, runScaffoldFlow, } from "./cli-core.js";
@@ -7,6 +7,7 @@ import { syncPersistenceRestArtifacts } from "./persistence-rest-artifacts.js";
7
7
  import { getPackageVersions } from "./package-versions.js";
8
8
  import { getStarterManifestFiles, stringifyStarterManifest } from "./starter-manifests.js";
9
9
  import { OFFICIAL_WORKSPACE_TEMPLATE_PACKAGE, PROJECT_TOOLS_PACKAGE_ROOT, } from "./template-registry.js";
10
+ import { getScaffoldTemplateVariableGroups } from "./scaffold-template-variable-groups.js";
10
11
  const EPHEMERAL_NODE_MODULES_LINK_TYPE = process.platform === "win32" ? "junction" : "dir";
11
12
  /**
12
13
  * Ensures the scaffold target directory exists and is empty unless explicitly allowed.
@@ -71,8 +72,11 @@ export async function writeStarterManifestFiles(targetDir, templateId, variables
71
72
  * @returns A promise that resolves after any required persistence artifacts are generated.
72
73
  */
73
74
  export async function seedBuiltInPersistenceArtifacts(targetDir, templateId, variables) {
75
+ const compoundGroup = getScaffoldTemplateVariableGroups(variables).compound;
74
76
  const needsPersistenceArtifacts = templateId === "persistence" ||
75
- (templateId === "compound" && variables.compoundPersistenceEnabled === "true");
77
+ (templateId === "compound" &&
78
+ compoundGroup.enabled &&
79
+ compoundGroup.persistenceEnabled);
76
80
  if (!needsPersistenceArtifacts) {
77
81
  return;
78
82
  }
@@ -2,6 +2,7 @@ import { getPrimaryDevelopmentScript } from './local-dev-presets.js';
2
2
  import { getCompoundExtensionWorkflowSection, getInitialCommitCommands, getInitialCommitNote, getOptionalOnboardingNote, getOptionalOnboardingSteps, getQuickStartWorkflowNote, getPhpRestExtensionPointsSection, getTemplateSourceOfTruthNote, } from './scaffold-onboarding.js';
3
3
  import { formatPackageExecCommand, formatInstallCommand, formatRunScript, } from './package-managers.js';
4
4
  import { getPackageVersions } from './package-versions.js';
5
+ import { getScaffoldTemplateVariableGroups } from './scaffold-template-variable-groups.js';
5
6
  /**
6
7
  * Builds the generated README markdown for one scaffolded project.
7
8
  *
@@ -12,26 +13,28 @@ import { getPackageVersions } from './package-versions.js';
12
13
  * @returns Markdown README content for the generated project root.
13
14
  */
14
15
  export function buildReadme(templateId, variables, packageManager, { withMigrationUi = false, withTestPreset = false, withWpEnv = false, } = {}) {
16
+ const variableGroups = getScaffoldTemplateVariableGroups(variables);
17
+ const compoundPersistenceEnabled = variableGroups.compound.enabled &&
18
+ variableGroups.compound.persistenceEnabled;
15
19
  const optionalOnboardingSteps = getOptionalOnboardingSteps(packageManager, templateId, {
16
- compoundPersistenceEnabled: variables.compoundPersistenceEnabled === 'true',
20
+ compoundPersistenceEnabled,
17
21
  });
18
22
  const initialCommitCommands = getInitialCommitCommands();
19
23
  const sourceOfTruthNote = getTemplateSourceOfTruthNote(templateId, {
20
- compoundPersistenceEnabled: variables.compoundPersistenceEnabled === 'true',
24
+ compoundPersistenceEnabled,
21
25
  });
22
- const compoundPersistenceEnabled = variables.compoundPersistenceEnabled === 'true';
23
- const publicPersistencePolicyNote = variables.isPublicPersistencePolicy === 'true'
26
+ const publicPersistencePolicyNote = variableGroups.persistence.enabled && variableGroups.persistence.auth.isPublic
24
27
  ? 'Public persistence writes use signed short-lived tokens, per-request ids, and coarse rate limiting by default. Add application-specific abuse controls before using the same pattern for high-value metrics or experiments.'
25
28
  : null;
26
- const alternateRenderTargetSection = variables.hasAlternateRenderTargets === 'true'
29
+ const alternateRenderTargetSection = variableGroups.alternateRenderTargets.enabled
27
30
  ? `## Alternate Render Targets\n\nThis scaffold keeps \`${templateId === 'compound' ? `src/blocks/${variables.slugKebabCase}/render.php` : 'src/render.php'}\` as the default web render boundary and also generates ${[
28
- variables.hasAlternateEmailRenderTarget === 'true'
31
+ variableGroups.alternateRenderTargets.hasEmail
29
32
  ? `\`${templateId === 'compound' ? `src/blocks/${variables.slugKebabCase}/render-email.php` : 'src/render-email.php'}\``
30
33
  : null,
31
- variables.hasAlternateMjmlRenderTarget === 'true'
34
+ variableGroups.alternateRenderTargets.hasMjml
32
35
  ? `\`${templateId === 'compound' ? `src/blocks/${variables.slugKebabCase}/render-mjml.php` : 'src/render-mjml.php'}\``
33
36
  : null,
34
- variables.hasAlternatePlainTextRenderTarget === 'true'
37
+ variableGroups.alternateRenderTargets.hasPlainText
35
38
  ? `\`${templateId === 'compound' ? `src/blocks/${variables.slugKebabCase}/render-text.php` : 'src/render-text.php'}\``
36
39
  : null,
37
40
  ]
@@ -0,0 +1,154 @@
1
+ export declare const SCAFFOLD_TEMPLATE_VARIABLE_GROUPS: unique symbol;
2
+ export type ScaffoldTemplateFamily = "basic" | "interactivity" | "persistence" | "compound" | "query-loop" | "external";
3
+ export interface ScaffoldSharedTemplateVariableGroup {
4
+ author: string;
5
+ blockMetadataVersion: string;
6
+ category: string;
7
+ cssClassName: string;
8
+ description: string;
9
+ descriptionJson: string;
10
+ frontendCssClassName: string;
11
+ icon: string;
12
+ keyword: string;
13
+ namespace: string;
14
+ pascalCase: string;
15
+ phpPrefix: string;
16
+ phpPrefixUpper: string;
17
+ slug: string;
18
+ slugCamelCase: string;
19
+ slugKebabCase: string;
20
+ slugSnakeCase: string;
21
+ textDomain: string;
22
+ title: string;
23
+ titleCase: string;
24
+ titleJson: string;
25
+ versions: {
26
+ apiClient: string;
27
+ blockRuntime: string;
28
+ blockTypes: string;
29
+ projectTools: string;
30
+ rest: string;
31
+ };
32
+ }
33
+ export interface ScaffoldAlternateRenderTargetVariableGroup {
34
+ csv: string;
35
+ enabled: boolean;
36
+ hasEmail: boolean;
37
+ hasMjml: boolean;
38
+ hasPlainText: boolean;
39
+ json: string;
40
+ targets: readonly string[];
41
+ }
42
+ export interface DisabledScaffoldCompoundVariableGroup {
43
+ enabled: false;
44
+ persistenceEnabled: false;
45
+ }
46
+ export interface EnabledScaffoldCompoundVariableGroup {
47
+ child: {
48
+ category: string;
49
+ cssClassName: string;
50
+ icon: string;
51
+ title: string;
52
+ titleJson: string;
53
+ };
54
+ enabled: true;
55
+ innerBlocks: {
56
+ description: string;
57
+ directInsert: boolean;
58
+ label: string;
59
+ orientation: "" | "horizontal" | "vertical";
60
+ orientationExpression: string;
61
+ preset: string;
62
+ templateLockExpression: string;
63
+ };
64
+ persistenceEnabled: boolean;
65
+ }
66
+ export type ScaffoldCompoundVariableGroup = DisabledScaffoldCompoundVariableGroup | EnabledScaffoldCompoundVariableGroup;
67
+ export interface DisabledScaffoldPersistenceVariableGroup {
68
+ enabled: false;
69
+ scope: "none";
70
+ }
71
+ export interface EnabledScaffoldPersistenceVariableGroup {
72
+ auth: {
73
+ bootstrapCredentialDeclarations: string;
74
+ descriptionJson: string;
75
+ intent: "authenticated" | "public-write-protected";
76
+ isAuthenticated: boolean;
77
+ isPublic: boolean;
78
+ mechanism: "public-signed-token" | "rest-nonce";
79
+ mode: "authenticated-rest-nonce" | "public-signed-token";
80
+ publicWriteRequestIdDeclaration: string;
81
+ };
82
+ dataStorageMode: "custom-table" | "post-meta";
83
+ enabled: true;
84
+ policy: "authenticated" | "public";
85
+ scope: "compound-parent" | "single";
86
+ }
87
+ export type ScaffoldPersistenceVariableGroup = DisabledScaffoldPersistenceVariableGroup | EnabledScaffoldPersistenceVariableGroup;
88
+ export interface DisabledScaffoldQueryLoopVariableGroup {
89
+ enabled: false;
90
+ }
91
+ export interface EnabledScaffoldQueryLoopVariableGroup {
92
+ allowedControls: readonly string[];
93
+ allowedControlsJson: string;
94
+ enabled: true;
95
+ postType: string;
96
+ postTypeJson: string;
97
+ variationNamespace: string;
98
+ variationNamespaceJson: string;
99
+ }
100
+ export type ScaffoldQueryLoopVariableGroup = DisabledScaffoldQueryLoopVariableGroup | EnabledScaffoldQueryLoopVariableGroup;
101
+ interface ScaffoldTemplateVariableGroupsBase {
102
+ alternateRenderTargets: ScaffoldAlternateRenderTargetVariableGroup;
103
+ shared: ScaffoldSharedTemplateVariableGroup;
104
+ template: {
105
+ description: string;
106
+ };
107
+ }
108
+ type DisabledTemplateFamilyGroups<TFamily extends "basic" | "interactivity" | "external"> = ScaffoldTemplateVariableGroupsBase & {
109
+ compound: DisabledScaffoldCompoundVariableGroup;
110
+ persistence: DisabledScaffoldPersistenceVariableGroup;
111
+ queryLoop: DisabledScaffoldQueryLoopVariableGroup;
112
+ templateFamily: TFamily;
113
+ };
114
+ export type BasicScaffoldTemplateVariableGroups = DisabledTemplateFamilyGroups<"basic">;
115
+ export type InteractivityScaffoldTemplateVariableGroups = DisabledTemplateFamilyGroups<"interactivity">;
116
+ export interface PersistenceScaffoldTemplateVariableGroups extends ScaffoldTemplateVariableGroupsBase {
117
+ compound: DisabledScaffoldCompoundVariableGroup;
118
+ persistence: EnabledScaffoldPersistenceVariableGroup & {
119
+ scope: "single";
120
+ };
121
+ queryLoop: DisabledScaffoldQueryLoopVariableGroup;
122
+ templateFamily: "persistence";
123
+ }
124
+ export interface CompoundScaffoldTemplateVariableGroups extends ScaffoldTemplateVariableGroupsBase {
125
+ compound: EnabledScaffoldCompoundVariableGroup;
126
+ persistence: DisabledScaffoldPersistenceVariableGroup | (EnabledScaffoldPersistenceVariableGroup & {
127
+ scope: "compound-parent";
128
+ });
129
+ queryLoop: DisabledScaffoldQueryLoopVariableGroup;
130
+ templateFamily: "compound";
131
+ }
132
+ export interface QueryLoopScaffoldTemplateVariableGroups extends ScaffoldTemplateVariableGroupsBase {
133
+ compound: DisabledScaffoldCompoundVariableGroup;
134
+ persistence: DisabledScaffoldPersistenceVariableGroup;
135
+ queryLoop: EnabledScaffoldQueryLoopVariableGroup;
136
+ templateFamily: "query-loop";
137
+ }
138
+ export type ExternalScaffoldTemplateVariableGroups = DisabledTemplateFamilyGroups<"external">;
139
+ export type ScaffoldTemplateVariableGroups = BasicScaffoldTemplateVariableGroups | InteractivityScaffoldTemplateVariableGroups | PersistenceScaffoldTemplateVariableGroups | CompoundScaffoldTemplateVariableGroups | QueryLoopScaffoldTemplateVariableGroups | ExternalScaffoldTemplateVariableGroups;
140
+ export interface ScaffoldTemplateVariableGroupsCarrier {
141
+ readonly [SCAFFOLD_TEMPLATE_VARIABLE_GROUPS]: ScaffoldTemplateVariableGroups;
142
+ }
143
+ export type CompoundScaffoldTemplateVariablesLike = ScaffoldTemplateVariableGroupsCarrier & {
144
+ slugKebabCase: string;
145
+ };
146
+ export type PersistenceScaffoldTemplateVariablesLike = ScaffoldTemplateVariableGroupsCarrier & {
147
+ namespace: string;
148
+ pascalCase: string;
149
+ slugKebabCase: string;
150
+ title: string;
151
+ };
152
+ export declare function attachScaffoldTemplateVariableGroups<TVariables extends Record<string, string>>(variables: TVariables, groups: ScaffoldTemplateVariableGroups): TVariables & ScaffoldTemplateVariableGroupsCarrier;
153
+ export declare function getScaffoldTemplateVariableGroups(variables: ScaffoldTemplateVariableGroupsCarrier): ScaffoldTemplateVariableGroups;
154
+ export {};
@@ -0,0 +1,13 @@
1
+ export const SCAFFOLD_TEMPLATE_VARIABLE_GROUPS = Symbol("wp-typia.scaffold-template-variable-groups");
2
+ export function attachScaffoldTemplateVariableGroups(variables, groups) {
3
+ Object.defineProperty(variables, SCAFFOLD_TEMPLATE_VARIABLE_GROUPS, {
4
+ configurable: false,
5
+ enumerable: false,
6
+ value: groups,
7
+ writable: false,
8
+ });
9
+ return variables;
10
+ }
11
+ export function getScaffoldTemplateVariableGroups(variables) {
12
+ return variables[SCAFFOLD_TEMPLATE_VARIABLE_GROUPS];
13
+ }
@@ -5,6 +5,7 @@ import { BUILTIN_BLOCK_METADATA_VERSION, COMPOUND_CHILD_BLOCK_METADATA_DEFAULTS,
5
5
  import { DEFAULT_COMPOUND_INNER_BLOCKS_PRESET_ID, getCompoundInnerBlocksPresetDefinition, } from './compound-inner-blocks.js';
6
6
  import { getTemplateById, isBuiltInTemplateId, } from './template-registry.js';
7
7
  import { toPascalCase, toSnakeCase, } from './string-case.js';
8
+ import { attachScaffoldTemplateVariableGroups } from "./scaffold-template-variable-groups.js";
8
9
  /**
9
10
  * Build the normalized template variables used by scaffold rendering.
10
11
  *
@@ -57,7 +58,7 @@ export function getTemplateVariables(templateId, answers) {
57
58
  const persistencePolicy = templateId === 'persistence' || compoundPersistenceEnabled
58
59
  ? answers.persistencePolicy ?? 'authenticated'
59
60
  : 'authenticated';
60
- return {
61
+ const flatVariables = {
61
62
  alternateRenderTargetsCsv: '',
62
63
  alternateRenderTargetsJson: '[]',
63
64
  apiClientPackageVersion,
@@ -136,4 +137,60 @@ export function getTemplateVariables(templateId, answers) {
136
137
  titleCase: pascalCase,
137
138
  persistencePolicy,
138
139
  };
140
+ return attachScaffoldTemplateVariableGroups(flatVariables, {
141
+ alternateRenderTargets: {
142
+ csv: '',
143
+ enabled: false,
144
+ hasEmail: false,
145
+ hasMjml: false,
146
+ hasPlainText: false,
147
+ json: '[]',
148
+ targets: [],
149
+ },
150
+ compound: {
151
+ enabled: false,
152
+ persistenceEnabled: false,
153
+ },
154
+ persistence: {
155
+ enabled: false,
156
+ scope: 'none',
157
+ },
158
+ queryLoop: {
159
+ enabled: false,
160
+ },
161
+ shared: {
162
+ author: answers.author.trim(),
163
+ blockMetadataVersion: BUILTIN_BLOCK_METADATA_VERSION,
164
+ category: metadataDefaults?.category ?? template?.defaultCategory ?? 'widgets',
165
+ cssClassName,
166
+ description,
167
+ descriptionJson: JSON.stringify(description),
168
+ frontendCssClassName: buildFrontendCssClassName(cssClassName),
169
+ icon: metadataDefaults?.icon ?? 'smiley',
170
+ keyword: slug.replace(/-/g, ' '),
171
+ namespace,
172
+ pascalCase,
173
+ phpPrefix,
174
+ phpPrefixUpper,
175
+ slug,
176
+ slugCamelCase: pascalCase.charAt(0).toLowerCase() + pascalCase.slice(1),
177
+ slugKebabCase: slug,
178
+ slugSnakeCase,
179
+ textDomain,
180
+ title,
181
+ titleCase: pascalCase,
182
+ titleJson: JSON.stringify(title),
183
+ versions: {
184
+ apiClient: apiClientPackageVersion,
185
+ blockRuntime: blockRuntimePackageVersion,
186
+ blockTypes: blockTypesPackageVersion,
187
+ projectTools: projectToolsPackageVersion,
188
+ rest: restPackageVersion,
189
+ },
190
+ },
191
+ template: {
192
+ description: template?.description ?? 'External scaffold template variables',
193
+ },
194
+ templateFamily: 'external',
195
+ });
139
196
  }
@@ -1,4 +1,5 @@
1
1
  import type { PackageManagerId } from "./package-managers.js";
2
+ import type { ScaffoldTemplateVariableGroupsCarrier } from "./scaffold-template-variable-groups.js";
2
3
  /**
3
4
  * User-facing scaffold answers before template rendering.
4
5
  *
@@ -29,7 +30,7 @@ export type PersistencePolicy = (typeof PERSISTENCE_POLICIES)[number];
29
30
  /**
30
31
  * Normalized template variables shared by built-in and remote scaffold flows.
31
32
  */
32
- export interface ScaffoldTemplateVariables extends Record<string, string> {
33
+ export interface FlatScaffoldTemplateVariables extends Record<string, string> {
33
34
  alternateRenderTargetsCsv: string;
34
35
  alternateRenderTargetsJson: string;
35
36
  apiClientPackageVersion: string;
@@ -94,6 +95,8 @@ export interface ScaffoldTemplateVariables extends Record<string, string> {
94
95
  titleCase: string;
95
96
  persistencePolicy: PersistencePolicy;
96
97
  }
98
+ export interface ScaffoldTemplateVariables extends FlatScaffoldTemplateVariables, ScaffoldTemplateVariableGroupsCarrier {
99
+ }
97
100
  /**
98
101
  * Resolve scaffold template input from either built-in template ids or custom
99
102
  * template identifiers such as local paths, GitHub refs, and npm packages.
@@ -176,6 +179,7 @@ export interface ScaffoldProjectResult {
176
179
  export { buildBlockCssClassName } from "./scaffold-identifiers.js";
177
180
  export { collectScaffoldAnswers, detectAuthor, getDefaultAnswers, resolvePackageManagerId, resolveTemplateId, } from "./scaffold-answer-resolution.js";
178
181
  export { getTemplateVariables } from "./scaffold-template-variables.js";
182
+ export { getScaffoldTemplateVariableGroups, type BasicScaffoldTemplateVariableGroups, type CompoundScaffoldTemplateVariableGroups, type ExternalScaffoldTemplateVariableGroups, type InteractivityScaffoldTemplateVariableGroups, type PersistenceScaffoldTemplateVariableGroups, type QueryLoopScaffoldTemplateVariableGroups, type ScaffoldTemplateFamily, type ScaffoldTemplateVariableGroups, type ScaffoldTemplateVariableGroupsCarrier, } from "./scaffold-template-variable-groups.js";
179
183
  export declare function isDataStorageMode(value: string): value is DataStorageMode;
180
184
  export declare function isPersistencePolicy(value: string): value is PersistencePolicy;
181
185
  export declare function scaffoldProject({ projectDir, templateId, answers, alternateRenderTargets, dataStorageMode, persistencePolicy, packageManager, externalLayerId, externalLayerSource, externalLayerSourceLabel, repositoryReference, cwd, allowExistingDir, noInstall, installDependencies, onProgress, variant, withMigrationUi, withTestPreset, withWpEnv, }: ScaffoldProjectOptions): Promise<ScaffoldProjectResult>;
@@ -12,6 +12,7 @@ import { OFFICIAL_WORKSPACE_TEMPLATE_PACKAGE, isBuiltInTemplateId, } from "./tem
12
12
  import { resolveTemplateSource } from "./template-source.js";
13
13
  import { BlockGeneratorService, } from "./block-generator-service.js";
14
14
  import { getTemplateVariables } from "./scaffold-template-variables.js";
15
+ import { getScaffoldTemplateVariableGroups } from "./scaffold-template-variable-groups.js";
15
16
  import { assertExternalLayerCompositionOptions, } from "./cli-validation.js";
16
17
  const WORKSPACE_TEMPLATE_ALIAS = "workspace";
17
18
  export const DATA_STORAGE_MODES = ["post-meta", "custom-table"];
@@ -19,6 +20,7 @@ export const PERSISTENCE_POLICIES = ["authenticated", "public"];
19
20
  export { buildBlockCssClassName } from "./scaffold-identifiers.js";
20
21
  export { collectScaffoldAnswers, detectAuthor, getDefaultAnswers, resolvePackageManagerId, resolveTemplateId, } from "./scaffold-answer-resolution.js";
21
22
  export { getTemplateVariables } from "./scaffold-template-variables.js";
23
+ export { getScaffoldTemplateVariableGroups, } from "./scaffold-template-variable-groups.js";
22
24
  export function isDataStorageMode(value) {
23
25
  return DATA_STORAGE_MODES.includes(value);
24
26
  }
@@ -164,8 +166,10 @@ export async function scaffoldProject({ projectDir, templateId, answers, alterna
164
166
  await fsp.writeFile(gitignorePath, mergeTextLines(buildGitignore(), existingGitignore), "utf8");
165
167
  await normalizePackageJson(projectDir, resolvedPackageManager);
166
168
  if (isBuiltInTemplate) {
169
+ const variableGroups = getScaffoldTemplateVariableGroups(variables);
167
170
  await applyGeneratedProjectDxPackageJson({
168
- compoundPersistenceEnabled: variables.compoundPersistenceEnabled === "true",
171
+ compoundPersistenceEnabled: variableGroups.compound.enabled &&
172
+ variableGroups.compound.persistenceEnabled,
169
173
  packageManager: resolvedPackageManager,
170
174
  projectDir,
171
175
  templateId: resolvedTemplateId,