@wp-typia/project-tools 0.19.0 → 0.19.1

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.
@@ -5,6 +5,7 @@ import { DEFAULT_COMPOUND_INNER_BLOCKS_PRESET_ID, getCompoundInnerBlocksPresetDe
5
5
  import { getTemplateById, } from "./template-registry.js";
6
6
  import { toPascalCase, toSnakeCase, } from "./string-case.js";
7
7
  import { buildBlockCssClassName, buildFrontendCssClassName, resolveScaffoldIdentifiers, } from "./scaffold-identifiers.js";
8
+ import { attachScaffoldTemplateVariableGroups } from "./scaffold-template-variable-groups.js";
8
9
  function getBuiltInPersistenceSpec({ templateId, dataStorageMode, persistencePolicy, }) {
9
10
  if (templateId === "persistence") {
10
11
  return {
@@ -114,13 +115,14 @@ export function buildTemplateVariablesFromBlockSpec(spec) {
114
115
  const compoundChildCssClassName = buildBlockCssClassName(namespace, `${slug}-item`);
115
116
  const compoundInnerBlocksPreset = spec.compound.innerBlocksPreset;
116
117
  const compoundInnerBlocksPresetDefinition = getCompoundInnerBlocksPresetDefinition(compoundInnerBlocksPreset);
118
+ const compoundInnerBlocksOrientation = compoundInnerBlocksPresetDefinition.orientation ?? "";
117
119
  const persistenceEnabled = spec.persistence.enabled;
118
120
  const dataStorageMode = persistenceEnabled ? spec.persistence.dataStorageMode : "custom-table";
119
121
  const persistencePolicy = persistenceEnabled
120
122
  ? spec.persistence.persistencePolicy
121
123
  : "authenticated";
122
124
  const queryVariationNamespace = `${namespace}/${slug}`;
123
- return {
125
+ const flatVariables = {
124
126
  alternateRenderTargetsCsv: formatAlternateRenderTargets(alternateRenderTargets),
125
127
  alternateRenderTargetsJson: JSON.stringify(alternateRenderTargets),
126
128
  apiClientPackageVersion,
@@ -137,7 +139,7 @@ export function buildTemplateVariablesFromBlockSpec(spec) {
137
139
  compoundChildTitleJson: JSON.stringify(compoundChildTitle),
138
140
  compoundPersistenceEnabled: spec.template.family === "compound" && persistenceEnabled ? "true" : "false",
139
141
  compoundInnerBlocksDirectInsert: compoundInnerBlocksPresetDefinition.directInsert ? "true" : "false",
140
- compoundInnerBlocksOrientation: compoundInnerBlocksPresetDefinition.orientation ?? "",
142
+ compoundInnerBlocksOrientation: compoundInnerBlocksOrientation,
141
143
  compoundInnerBlocksOrientationExpression: compoundInnerBlocksPresetDefinition.orientation
142
144
  ? `'${compoundInnerBlocksPresetDefinition.orientation}'`
143
145
  : "undefined",
@@ -205,4 +207,206 @@ export function buildTemplateVariablesFromBlockSpec(spec) {
205
207
  titleCase: pascalCase,
206
208
  persistencePolicy,
207
209
  };
210
+ const shared = {
211
+ author: spec.project.author,
212
+ blockMetadataVersion: BUILTIN_BLOCK_METADATA_VERSION,
213
+ category: spec.metadata.category,
214
+ cssClassName,
215
+ description: spec.metadata.description,
216
+ descriptionJson: flatVariables.descriptionJson,
217
+ frontendCssClassName: flatVariables.frontendCssClassName,
218
+ icon: spec.metadata.icon,
219
+ keyword: spec.metadata.keyword,
220
+ namespace,
221
+ pascalCase,
222
+ phpPrefix,
223
+ phpPrefixUpper,
224
+ slug,
225
+ slugCamelCase: flatVariables.slugCamelCase,
226
+ slugKebabCase: slug,
227
+ slugSnakeCase,
228
+ textDomain,
229
+ title,
230
+ titleCase: pascalCase,
231
+ titleJson: flatVariables.titleJson,
232
+ versions: {
233
+ apiClient: apiClientPackageVersion,
234
+ blockRuntime: blockRuntimePackageVersion,
235
+ blockTypes: blockTypesPackageVersion,
236
+ projectTools: projectToolsPackageVersion,
237
+ rest: restPackageVersion,
238
+ },
239
+ };
240
+ const alternateRenderTargetsGroup = {
241
+ csv: flatVariables.alternateRenderTargetsCsv,
242
+ enabled: alternateRenderTargets.length > 0,
243
+ hasEmail: hasAlternateEmailRenderTarget,
244
+ hasMjml: hasAlternateMjmlRenderTarget,
245
+ hasPlainText: hasAlternatePlainTextRenderTarget,
246
+ json: flatVariables.alternateRenderTargetsJson,
247
+ targets: alternateRenderTargets,
248
+ };
249
+ const compoundGroup = {
250
+ child: {
251
+ category: COMPOUND_CHILD_BLOCK_METADATA_DEFAULTS.category,
252
+ cssClassName: compoundChildCssClassName,
253
+ icon: COMPOUND_CHILD_BLOCK_METADATA_DEFAULTS.icon,
254
+ title: compoundChildTitle,
255
+ titleJson: JSON.stringify(compoundChildTitle),
256
+ },
257
+ enabled: true,
258
+ innerBlocks: {
259
+ description: compoundInnerBlocksPresetDefinition.description,
260
+ directInsert: compoundInnerBlocksPresetDefinition.directInsert,
261
+ label: compoundInnerBlocksPresetDefinition.label,
262
+ orientation: compoundInnerBlocksOrientation,
263
+ orientationExpression: compoundInnerBlocksPresetDefinition.orientation
264
+ ? `'${compoundInnerBlocksPresetDefinition.orientation}'`
265
+ : "undefined",
266
+ preset: compoundInnerBlocksPreset,
267
+ templateLockExpression: compoundInnerBlocksPresetDefinition.templateLock === false
268
+ ? "false"
269
+ : `'${compoundInnerBlocksPresetDefinition.templateLock}'`,
270
+ },
271
+ persistenceEnabled: persistenceEnabled,
272
+ };
273
+ const persistenceGroup = persistenceEnabled
274
+ ? {
275
+ auth: {
276
+ bootstrapCredentialDeclarations: flatVariables.bootstrapCredentialDeclarations,
277
+ descriptionJson: flatVariables.persistencePolicyDescriptionJson,
278
+ intent: flatVariables.restWriteAuthIntent,
279
+ isAuthenticated: persistencePolicy === "authenticated",
280
+ isPublic: persistencePolicy === "public",
281
+ mechanism: flatVariables.restWriteAuthMechanism,
282
+ mode: flatVariables.restWriteAuthMode,
283
+ publicWriteRequestIdDeclaration: flatVariables.publicWriteRequestIdDeclaration,
284
+ },
285
+ dataStorageMode,
286
+ enabled: true,
287
+ policy: persistencePolicy,
288
+ scope: spec.persistence.scope,
289
+ }
290
+ : null;
291
+ const queryLoopGroup = {
292
+ allowedControls: spec.queryLoop.allowedControls,
293
+ allowedControlsJson: flatVariables.queryAllowedControlsJson,
294
+ enabled: true,
295
+ postType: spec.queryLoop.postType,
296
+ postTypeJson: flatVariables.queryPostTypeJson,
297
+ variationNamespace: queryVariationNamespace,
298
+ variationNamespaceJson: flatVariables.queryVariationNamespaceJson,
299
+ };
300
+ switch (spec.template.family) {
301
+ case "basic":
302
+ return attachScaffoldTemplateVariableGroups(flatVariables, {
303
+ alternateRenderTargets: alternateRenderTargetsGroup,
304
+ compound: {
305
+ enabled: false,
306
+ persistenceEnabled: false,
307
+ },
308
+ persistence: {
309
+ enabled: false,
310
+ scope: "none",
311
+ },
312
+ queryLoop: {
313
+ enabled: false,
314
+ },
315
+ shared,
316
+ template: {
317
+ description: spec.template.description,
318
+ },
319
+ templateFamily: "basic",
320
+ });
321
+ case "interactivity":
322
+ return attachScaffoldTemplateVariableGroups(flatVariables, {
323
+ alternateRenderTargets: alternateRenderTargetsGroup,
324
+ compound: {
325
+ enabled: false,
326
+ persistenceEnabled: false,
327
+ },
328
+ persistence: {
329
+ enabled: false,
330
+ scope: "none",
331
+ },
332
+ queryLoop: {
333
+ enabled: false,
334
+ },
335
+ shared,
336
+ template: {
337
+ description: spec.template.description,
338
+ },
339
+ templateFamily: "interactivity",
340
+ });
341
+ case "persistence": {
342
+ if (persistenceGroup === null) {
343
+ throw new Error("Persistence scaffolds must provide persistence template variables.");
344
+ }
345
+ return attachScaffoldTemplateVariableGroups(flatVariables, {
346
+ alternateRenderTargets: alternateRenderTargetsGroup,
347
+ compound: {
348
+ enabled: false,
349
+ persistenceEnabled: false,
350
+ },
351
+ persistence: {
352
+ ...persistenceGroup,
353
+ scope: "single",
354
+ },
355
+ queryLoop: {
356
+ enabled: false,
357
+ },
358
+ shared,
359
+ template: {
360
+ description: spec.template.description,
361
+ },
362
+ templateFamily: "persistence",
363
+ });
364
+ }
365
+ case "compound": {
366
+ const compoundPersistenceGroup = persistenceGroup === null
367
+ ? {
368
+ enabled: false,
369
+ scope: "none",
370
+ }
371
+ : {
372
+ ...persistenceGroup,
373
+ scope: "compound-parent",
374
+ };
375
+ return attachScaffoldTemplateVariableGroups(flatVariables, {
376
+ alternateRenderTargets: alternateRenderTargetsGroup,
377
+ compound: compoundGroup,
378
+ persistence: compoundPersistenceGroup,
379
+ queryLoop: {
380
+ enabled: false,
381
+ },
382
+ shared,
383
+ template: {
384
+ description: spec.template.description,
385
+ },
386
+ templateFamily: "compound",
387
+ });
388
+ }
389
+ case "query-loop":
390
+ return attachScaffoldTemplateVariableGroups(flatVariables, {
391
+ alternateRenderTargets: alternateRenderTargetsGroup,
392
+ compound: {
393
+ enabled: false,
394
+ persistenceEnabled: false,
395
+ },
396
+ persistence: {
397
+ enabled: false,
398
+ scope: "none",
399
+ },
400
+ queryLoop: queryLoopGroup,
401
+ shared,
402
+ template: {
403
+ description: spec.template.description,
404
+ },
405
+ templateFamily: "query-loop",
406
+ });
407
+ default: {
408
+ const unreachableTemplateFamily = spec.template.family;
409
+ throw new Error(`Unhandled scaffold template family: ${unreachableTemplateFamily}`);
410
+ }
411
+ }
208
412
  }
@@ -1,5 +1,6 @@
1
1
  import { buildBasicAttributes, buildBlockJsonAttributes, buildCompoundChildAttributes, buildCompoundParentAttributes, buildInteractivityAttributes, buildManifestDocument, buildPersistenceAttributes, DEFAULT_COMPOUND_CHILD_BODY_PLACEHOLDER, } from "./built-in-block-artifact-documents.js";
2
2
  import { buildBasicTypesSource, buildCompoundChildTypesSource, buildCompoundTypesSource, buildInteractivityTypesSource, buildPersistenceTypesSource, } from "./built-in-block-artifact-types.js";
3
+ import { getScaffoldTemplateVariableGroups } from "./scaffold-template-variable-groups.js";
3
4
  function stringifyBlockJsonDocument(document) {
4
5
  return `${JSON.stringify(document, null, "\t")}\n`;
5
6
  }
@@ -103,7 +104,8 @@ function buildPersistenceArtifact(variables) {
103
104
  }
104
105
  function buildCompoundParentArtifact(variables) {
105
106
  const attributes = buildCompoundParentAttributes(variables);
106
- const persistenceEnabled = variables.compoundPersistenceEnabled === "true";
107
+ const compoundGroup = getScaffoldTemplateVariableGroups(variables).compound;
108
+ const persistenceEnabled = compoundGroup.enabled && compoundGroup.persistenceEnabled;
107
109
  return {
108
110
  blockJsonDocument: {
109
111
  $schema: "https://schemas.wp.org/trunk/block.json",
@@ -1,5 +1,6 @@
1
1
  import { buildBuiltInNonTsArtifacts } from "./built-in-block-non-ts-artifacts.js";
2
2
  import { BASIC_EDIT_TEMPLATE, BASIC_INDEX_TEMPLATE, BASIC_SAVE_TEMPLATE, BASIC_VALIDATORS_TEMPLATE, BLOCK_METADATA_WRAPPER_TEMPLATE, COMPOUND_CHILD_EDIT_TEMPLATE, COMPOUND_CHILD_INDEX_TEMPLATE, COMPOUND_CHILD_SAVE_TEMPLATE, COMPOUND_CHILD_VALIDATORS_TEMPLATE, COMPOUND_CHILDREN_TEMPLATE, COMPOUND_LOCAL_HOOKS_TEMPLATE, COMPOUND_PARENT_EDIT_TEMPLATE, COMPOUND_PARENT_INDEX_TEMPLATE, COMPOUND_PARENT_SAVE_TEMPLATE, COMPOUND_PARENT_VALIDATORS_TEMPLATE, COMPOUND_PERSISTENCE_PARENT_EDIT_TEMPLATE, COMPOUND_PERSISTENCE_PARENT_INTERACTIVITY_TEMPLATE, COMPOUND_PERSISTENCE_PARENT_SAVE_TEMPLATE, COMPOUND_PERSISTENCE_PARENT_VALIDATORS_TEMPLATE, INTERACTIVITY_EDIT_TEMPLATE, INTERACTIVITY_INDEX_TEMPLATE, INTERACTIVITY_SAVE_TEMPLATE, INTERACTIVITY_SCRIPT_TEMPLATE, INTERACTIVITY_VALIDATORS_TEMPLATE, MANIFEST_DEFAULTS_DOCUMENT_WRAPPER_TEMPLATE, MANIFEST_DOCUMENT_WRAPPER_TEMPLATE, PERSISTENCE_EDIT_TEMPLATE, PERSISTENCE_INDEX_TEMPLATE, PERSISTENCE_INTERACTIVITY_TEMPLATE, PERSISTENCE_SAVE_TEMPLATE, PERSISTENCE_VALIDATORS_TEMPLATE, QUERY_LOOP_INDEX_TEMPLATE, SHARED_HOOKS_TEMPLATE, } from "./built-in-block-code-templates.js";
3
+ import { getScaffoldTemplateVariableGroups } from "./scaffold-template-variable-groups.js";
3
4
  import { renderMustacheTemplateString } from "./template-render.js";
4
5
  function renderCodeTemplate(template, variables) {
5
6
  const rendered = renderMustacheTemplateString(template, variables);
@@ -98,7 +99,8 @@ function buildInteractivityCodeArtifacts(variables) {
98
99
  function buildCompoundCodeArtifacts(variables) {
99
100
  const parentBasePath = `src/blocks/${variables.slugKebabCase}`;
100
101
  const childBasePath = `src/blocks/${variables.slugKebabCase}-item`;
101
- const compoundPersistenceEnabled = variables.compoundPersistenceEnabled === "true";
102
+ const compoundGroup = getScaffoldTemplateVariableGroups(variables).compound;
103
+ const compoundPersistenceEnabled = compoundGroup.enabled && compoundGroup.persistenceEnabled;
102
104
  return ensureUniqueArtifactPaths([
103
105
  createCodeArtifact("src/hooks.ts", SHARED_HOOKS_TEMPLATE, variables),
104
106
  ...createTypedJsonWrapperArtifacts(parentBasePath, variables),
@@ -1,6 +1,5 @@
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 { syncBlockMetadata, } from "@wp-typia/block-runtime/metadata-core";
6
5
  import { ensureMigrationDirectories, parseMigrationConfig, writeInitialMigrationScaffold, writeMigrationConfig, } from "./migration-project.js";
@@ -9,6 +8,7 @@ import { snapshotProjectVersion } from "./migrations.js";
9
8
  import { getDefaultAnswers, scaffoldProject } from "./scaffold.js";
10
9
  import { copyInterpolatedDirectory, listInterpolatedDirectoryOutputs, } from "./template-render.js";
11
10
  import { appendWorkspaceInventoryEntries, } from "./workspace-inventory.js";
11
+ import { createManagedTempRoot } from "./temp-roots.js";
12
12
  import { resolveWorkspaceProject, } from "./workspace-project.js";
13
13
  import { ADD_BLOCK_TEMPLATE_IDS, buildWorkspacePhpPrefix, isAddBlockTemplateId, patchFile, readOptionalFile, rollbackWorkspaceMutation, snapshotWorkspaceFiles, } from "./cli-add-shared.js";
14
14
  import { resolveNonEmptyNormalizedBlockSlug, } from "./scaffold-identifiers.js";
@@ -310,6 +310,7 @@ export async function runAddBlockCommand({ alternateRenderTargets, blockName, cw
310
310
  selectExternalLayerId,
311
311
  });
312
312
  let tempRoot = "";
313
+ let cleanupTempRoot;
313
314
  try {
314
315
  const normalizedSlug = resolveNonEmptyNormalizedBlockSlug({
315
316
  input: blockName,
@@ -317,7 +318,10 @@ export async function runAddBlockCommand({ alternateRenderTargets, blockName, cw
317
318
  usage: "wp-typia add block <name> --template <family>",
318
319
  });
319
320
  const defaults = getDefaultAnswers(normalizedSlug, resolvedTemplateId);
320
- tempRoot = await fsp.mkdtemp(path.join(os.tmpdir(), "wp-typia-add-block-"));
321
+ ({
322
+ path: tempRoot,
323
+ cleanup: cleanupTempRoot,
324
+ } = await createManagedTempRoot("wp-typia-add-block-"));
321
325
  const tempProjectDir = path.join(tempRoot, normalizedSlug);
322
326
  const blockConfigPath = path.join(workspace.projectDir, "scripts", "block-config.ts");
323
327
  const migrationConfigPath = path.join(workspace.projectDir, "src", "migrations", "config.ts");
@@ -397,9 +401,11 @@ export async function runAddBlockCommand({ alternateRenderTargets, blockName, cw
397
401
  }
398
402
  }
399
403
  finally {
400
- await resolvedExternalLayerSelection.cleanup?.();
401
- if (tempRoot) {
402
- await fsp.rm(tempRoot, { force: true, recursive: true });
404
+ try {
405
+ await resolvedExternalLayerSelection.cleanup?.();
406
+ }
407
+ finally {
408
+ await cleanupTempRoot?.();
403
409
  }
404
410
  }
405
411
  }
@@ -2,10 +2,24 @@
2
2
  * Shared human-readable diagnostics for non-interactive `wp-typia` CLI flows.
3
3
  */
4
4
  export interface CliDiagnosticMessage {
5
+ code: CliDiagnosticCode;
5
6
  command: string;
6
7
  detailLines: string[];
7
8
  summary: string;
8
9
  }
10
+ export declare const CLI_DIAGNOSTIC_CODES: {
11
+ readonly COMMAND_EXECUTION: "command-execution";
12
+ readonly CONFIGURATION_MISSING: "configuration-missing";
13
+ readonly DEPENDENCIES_NOT_INSTALLED: "dependencies-not-installed";
14
+ readonly DOCTOR_CHECK_FAILED: "doctor-check-failed";
15
+ readonly INVALID_ARGUMENT: "invalid-argument";
16
+ readonly INVALID_COMMAND: "invalid-command";
17
+ readonly MISSING_ARGUMENT: "missing-argument";
18
+ readonly MISSING_BUILD_ARTIFACT: "missing-build-artifact";
19
+ readonly OUTSIDE_PROJECT_ROOT: "outside-project-root";
20
+ readonly UNSUPPORTED_COMMAND: "unsupported-command";
21
+ };
22
+ export type CliDiagnosticCode = (typeof CLI_DIAGNOSTIC_CODES)[keyof typeof CLI_DIAGNOSTIC_CODES];
9
23
  type DoctorCheckLike = {
10
24
  detail: string;
11
25
  label: string;
@@ -15,6 +29,7 @@ type DoctorCheckLike = {
15
29
  * Structured CLI failure carrying a stable summary/detail layout.
16
30
  */
17
31
  export declare class CliDiagnosticError extends Error {
32
+ readonly code: CliDiagnosticCode;
18
33
  readonly command: string;
19
34
  readonly detailLines: string[];
20
35
  readonly summary: string;
@@ -29,6 +44,7 @@ export declare function isCliDiagnosticError(error: unknown): error is CliDiagno
29
44
  */
30
45
  export declare function createCliCommandError(options: {
31
46
  command: string;
47
+ code?: CliDiagnosticCode;
32
48
  detailLines?: string[];
33
49
  error?: unknown;
34
50
  summary?: string;
@@ -38,6 +54,16 @@ export declare function createCliCommandError(options: {
38
54
  * plain message so existing non-command failures keep working.
39
55
  */
40
56
  export declare function formatCliDiagnosticError(error: unknown): string;
57
+ export declare function serializeCliDiagnosticError(error: unknown): {
58
+ code: CliDiagnosticCode;
59
+ command?: string;
60
+ detailLines?: string[];
61
+ kind: "command-execution";
62
+ message: string;
63
+ name: string;
64
+ summary?: string;
65
+ tag: "CommandExecutionError";
66
+ };
41
67
  /**
42
68
  * Format one human-readable doctor check row.
43
69
  */
@@ -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
  }