@wp-typia/project-tools 0.24.11 → 0.24.13

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 (72) hide show
  1. package/dist/runtime/add/cli-add-block-config.js +7 -15
  2. package/dist/runtime/add/cli-add-block-package-json.d.ts +5 -0
  3. package/dist/runtime/add/cli-add-block-package-json.js +32 -0
  4. package/dist/runtime/add/cli-add-block.js +17 -4
  5. package/dist/runtime/add/cli-add-workspace-admin-view-scaffold.js +1 -5
  6. package/dist/runtime/add/cli-add-workspace-hooked-block.js +15 -20
  7. package/dist/runtime/add/cli-add-workspace-mutation.d.ts +15 -2
  8. package/dist/runtime/add/cli-add-workspace-mutation.js +10 -4
  9. package/dist/runtime/add/persistence-rest-artifacts.d.ts +32 -7
  10. package/dist/runtime/add/persistence-rest-artifacts.js +41 -15
  11. package/dist/runtime/cli/cli-diagnostics.d.ts +9 -0
  12. package/dist/runtime/cli/cli-diagnostics.js +8 -0
  13. package/dist/runtime/cli/cli-init-package-json.js +5 -1
  14. package/dist/runtime/doctor/cli-doctor-standalone-control-flow.d.ts +38 -0
  15. package/dist/runtime/doctor/cli-doctor-standalone-control-flow.js +365 -0
  16. package/dist/runtime/doctor/cli-doctor-standalone-rest.d.ts +21 -0
  17. package/dist/runtime/doctor/cli-doctor-standalone-rest.js +1354 -0
  18. package/dist/runtime/doctor/cli-doctor-standalone-shared.d.ts +24 -0
  19. package/dist/runtime/doctor/cli-doctor-standalone-shared.js +50 -0
  20. package/dist/runtime/doctor/cli-doctor-standalone.d.ts +32 -0
  21. package/dist/runtime/doctor/cli-doctor-standalone.js +2594 -0
  22. package/dist/runtime/doctor/cli-doctor-workspace.d.ts +5 -3
  23. package/dist/runtime/doctor/cli-doctor-workspace.js +27 -3
  24. package/dist/runtime/doctor/cli-doctor.d.ts +4 -2
  25. package/dist/runtime/doctor/cli-doctor.js +4 -2
  26. package/dist/runtime/shared/json-utils.d.ts +7 -0
  27. package/dist/runtime/shared/json-utils.js +10 -0
  28. package/dist/runtime/shared/package-json-types.d.ts +1 -1
  29. package/dist/runtime/shared/package-versions.d.ts +2 -0
  30. package/dist/runtime/shared/package-versions.js +2 -0
  31. package/dist/runtime/shared/php-utils.d.ts +39 -2
  32. package/dist/runtime/shared/php-utils.js +205 -6
  33. package/dist/runtime/templates/block-generator-service-core.d.ts +1 -1
  34. package/dist/runtime/templates/block-generator-service-core.js +9 -3
  35. package/dist/runtime/templates/block-generator-service-spec.d.ts +5 -0
  36. package/dist/runtime/templates/built-in-block-artifact-documents.d.ts +1 -1
  37. package/dist/runtime/templates/built-in-block-artifact-documents.js +1 -1
  38. package/dist/runtime/templates/built-in-block-artifact-types.js +2 -2
  39. package/dist/runtime/templates/built-in-block-artifacts.d.ts +1 -1
  40. package/dist/runtime/templates/built-in-block-artifacts.js +16 -12
  41. package/dist/runtime/templates/built-in-block-attribute-emitters.d.ts +5 -0
  42. package/dist/runtime/templates/built-in-block-attribute-emitters.js +113 -8
  43. package/dist/runtime/templates/built-in-block-attribute-specs.js +4 -9
  44. package/dist/runtime/templates/built-in-block-code-artifacts.js +6 -1
  45. package/dist/runtime/templates/built-in-block-code-templates/compound-persistence.d.ts +2 -2
  46. package/dist/runtime/templates/built-in-block-code-templates/compound-persistence.js +24 -1
  47. package/dist/runtime/templates/built-in-block-code-templates/persistence.d.ts +2 -2
  48. package/dist/runtime/templates/built-in-block-code-templates/persistence.js +22 -1
  49. package/dist/runtime/templates/built-in-block-non-ts-compound-templates.d.ts +2 -2
  50. package/dist/runtime/templates/built-in-block-non-ts-compound-templates.js +6 -2
  51. package/dist/runtime/templates/built-in-block-non-ts-persistence-templates.d.ts +2 -2
  52. package/dist/runtime/templates/built-in-block-non-ts-persistence-templates.js +6 -2
  53. package/dist/runtime/templates/cli-scaffold-emission.d.ts +7 -1
  54. package/dist/runtime/templates/cli-scaffold-emission.js +10 -1
  55. package/dist/runtime/templates/cli-templates.d.ts +47 -0
  56. package/dist/runtime/templates/cli-templates.js +50 -1
  57. package/dist/runtime/templates/local-dev-presets.js +1 -1
  58. package/dist/runtime/templates/scaffold-apply-utils.d.ts +4 -8
  59. package/dist/runtime/templates/scaffold-apply-utils.js +44 -28
  60. package/dist/runtime/templates/scaffold-compiler-artifacts.d.ts +17 -0
  61. package/dist/runtime/templates/scaffold-compiler-artifacts.js +81 -0
  62. package/dist/runtime/templates/scaffold-onboarding.d.ts +5 -0
  63. package/dist/runtime/templates/scaffold-onboarding.js +13 -2
  64. package/dist/runtime/templates/scaffold-template-variable-groups.d.ts +4 -0
  65. package/dist/runtime/templates/scaffold-template-variable-groups.js +7 -0
  66. package/dist/runtime/templates/scaffold.d.ts +3 -1
  67. package/dist/runtime/templates/scaffold.js +2 -1
  68. package/dist/runtime/templates/starter-manifests.js +1 -1
  69. package/package.json +5 -5
  70. package/templates/_shared/compound/core/scripts/add-compound-child.ts.mustache +1 -1
  71. package/templates/_shared/compound/persistence/package.json.mustache +1 -0
  72. package/templates/_shared/persistence/core/package.json.mustache +1 -0
@@ -2,6 +2,7 @@ import path from "node:path";
2
2
  import { SHARED_WORKSPACE_TEMPLATE_ROOT, } from "../templates/template-registry.js";
3
3
  import { isCompoundPersistenceEnabled } from "../templates/scaffold-template-variable-groups.js";
4
4
  import { quoteTsString, } from "./cli-add-shared.js";
5
+ import { buildPersistenceEndpointManifest } from "./persistence-rest-artifacts.js";
5
6
  export function buildServerTemplateRoot(persistencePolicy) {
6
7
  return path.join(SHARED_WORKSPACE_TEMPLATE_ROOT, persistencePolicy === "public" ? "persistence-public" : "persistence-auth");
7
8
  }
@@ -15,27 +16,18 @@ function buildSingleBlockConfigEntry(variables) {
15
16
  ].join("\n");
16
17
  }
17
18
  function buildPersistenceBlockConfigEntry(variables) {
19
+ const contractLines = Object.entries(buildPersistenceEndpointManifest(variables).contracts).flatMap(([contractName, contract]) => [
20
+ `\t\t\t\t${quoteTsString(contractName)}: {`,
21
+ `\t\t\t\t\tsourceTypeName: ${quoteTsString(contract.sourceTypeName)},`,
22
+ "\t\t\t\t},",
23
+ ]);
18
24
  return [
19
25
  "\t{",
20
26
  `\t\tapiTypesFile: ${quoteTsString(`src/blocks/${variables.slugKebabCase}/api-types.ts`)},`,
21
27
  `\t\tattributeTypeName: ${quoteTsString(`${variables.pascalCase}Attributes`)},`,
22
28
  "\t\trestManifest: defineEndpointManifest( {",
23
29
  "\t\t\tcontracts: {",
24
- "\t\t\t\t'bootstrap-query': {",
25
- `\t\t\t\t\tsourceTypeName: ${quoteTsString(`${variables.pascalCase}BootstrapQuery`)},`,
26
- "\t\t\t\t},",
27
- "\t\t\t\t'bootstrap-response': {",
28
- `\t\t\t\t\tsourceTypeName: ${quoteTsString(`${variables.pascalCase}BootstrapResponse`)},`,
29
- "\t\t\t\t},",
30
- "\t\t\t\t'state-query': {",
31
- `\t\t\t\t\tsourceTypeName: ${quoteTsString(`${variables.pascalCase}StateQuery`)},`,
32
- "\t\t\t\t},",
33
- "\t\t\t\t'state-response': {",
34
- `\t\t\t\t\tsourceTypeName: ${quoteTsString(`${variables.pascalCase}StateResponse`)},`,
35
- "\t\t\t\t},",
36
- "\t\t\t\t'write-state-request': {",
37
- `\t\t\t\t\tsourceTypeName: ${quoteTsString(`${variables.pascalCase}WriteStateRequest`)},`,
38
- "\t\t\t\t},",
30
+ ...contractLines,
39
31
  "\t\t\t},",
40
32
  "\t\t\tendpoints: [",
41
33
  "\t\t\t\t{",
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Ensure legacy workspaces can resolve the editor store used by generated
3
+ * persistence blocks. Existing dependency ranges stay authoritative.
4
+ */
5
+ export declare function ensurePersistentBlockIdentityDependency(projectDir: string, required: boolean): Promise<boolean>;
@@ -0,0 +1,32 @@
1
+ import path from "node:path";
2
+ import { detectJsonIndent, safeJsonParse } from "../shared/json-utils.js";
3
+ import { DEFAULT_WORDPRESS_DATA_VERSION } from "../shared/package-versions.js";
4
+ import { patchFile } from "./cli-add-shared.js";
5
+ /**
6
+ * Ensure legacy workspaces can resolve the editor store used by generated
7
+ * persistence blocks. Existing dependency ranges stay authoritative.
8
+ */
9
+ export async function ensurePersistentBlockIdentityDependency(projectDir, required) {
10
+ if (!required) {
11
+ return false;
12
+ }
13
+ const packageJsonPath = path.join(projectDir, "package.json");
14
+ let added = false;
15
+ await patchFile(packageJsonPath, (source) => {
16
+ const packageJson = safeJsonParse(source, {
17
+ context: "workspace package manifest",
18
+ filePath: packageJsonPath,
19
+ });
20
+ if (packageJson.dependencies?.["@wordpress/data"] ||
21
+ packageJson.devDependencies?.["@wordpress/data"]) {
22
+ return source;
23
+ }
24
+ packageJson.dependencies = {
25
+ ...(packageJson.dependencies ?? {}),
26
+ "@wordpress/data": DEFAULT_WORDPRESS_DATA_VERSION,
27
+ };
28
+ added = true;
29
+ return `${JSON.stringify(packageJson, null, detectJsonIndent(source))}\n`;
30
+ });
31
+ return added;
32
+ }
@@ -12,6 +12,7 @@ import { resolveWorkspaceProject, } from "../workspace/workspace-project.js";
12
12
  import { ADD_BLOCK_TEMPLATE_IDS, buildWorkspacePhpPrefix, isAddBlockTemplateId, patchFile, readOptionalFile, rollbackWorkspaceMutation, snapshotWorkspaceFiles, suggestAddBlockTemplateId, } from "./cli-add-shared.js";
13
13
  import { resolveNonEmptyNormalizedBlockSlug, } from "../templates/scaffold-identifiers.js";
14
14
  import { buildConfigEntries, buildMigrationBlocks, buildServerTemplateRoot, } from "./cli-add-block-config.js";
15
+ import { ensurePersistentBlockIdentityDependency } from "./cli-add-block-package-json.js";
15
16
  import { COMPOUND_SHARED_SUPPORT_FILES, collectLegacyCompoundValidatorPaths, ensureBlockConfigCanAddRestManifests, ensureCompoundWorkspaceSupportFiles, } from "./cli-add-block-legacy-validator.js";
16
17
  import { parseTemplateLocator, resolveTemplateSeed, } from "../templates/template-source.js";
17
18
  import { resolveExternalTemplateLayers, } from "../templates/template-layers.js";
@@ -340,6 +341,7 @@ export async function runAddBlockCommand({ alternateRenderTargets, blockName, cw
340
341
  cleanup: cleanupTempRoot,
341
342
  } = await createManagedTempRoot("wp-typia-add-block-"));
342
343
  const tempProjectDir = path.join(tempRoot, normalizedSlug);
344
+ const packageJsonPath = path.join(workspace.projectDir, "package.json");
343
345
  const blockConfigPath = path.join(workspace.projectDir, "scripts", "block-config.ts");
344
346
  const migrationConfigPath = path.join(workspace.projectDir, "src", "migrations", "config.ts");
345
347
  const blockPhpPrefix = buildWorkspacePhpPrefix(workspace.workspace.phpPrefix, normalizedSlug);
@@ -373,6 +375,7 @@ export async function runAddBlockCommand({ alternateRenderTargets, blockName, cw
373
375
  packageManager: workspace.packageManager,
374
376
  persistencePolicy: persistencePolicy,
375
377
  projectDir: tempProjectDir,
378
+ seedCompilerArtifacts: false,
376
379
  templateId: resolvedTemplateId,
377
380
  });
378
381
  await assertAddBlockSupportsExternalLayerOutputs({
@@ -385,8 +388,12 @@ export async function runAddBlockCommand({ alternateRenderTargets, blockName, cw
385
388
  return scaffoldResult;
386
389
  })();
387
390
  await assertBlockTargetsDoNotExist(workspace.projectDir, resolvedTemplateId, result.variables);
391
+ const requiresPersistentIdentity = resolvedTemplateId === "persistence" ||
392
+ (resolvedTemplateId === "compound" &&
393
+ isCompoundPersistenceEnabled(result.variables));
388
394
  const mutationSnapshot = {
389
395
  fileSources: await snapshotWorkspaceFiles([
396
+ packageJsonPath,
390
397
  blockConfigPath,
391
398
  migrationConfigPath,
392
399
  ...compoundSupportPaths,
@@ -398,18 +405,24 @@ export async function runAddBlockCommand({ alternateRenderTargets, blockName, cw
398
405
  targetPaths: collectWorkspaceBlockPaths(workspace.projectDir, resolvedTemplateId, result.variables),
399
406
  };
400
407
  try {
408
+ const addedPersistentIdentityDependency = await ensurePersistentBlockIdentityDependency(workspace.projectDir, requiresPersistentIdentity);
401
409
  await copyScaffoldedBlockSlice(workspace.projectDir, resolvedTemplateId, tempProjectDir, result.variables, legacyCompoundValidatorPaths);
402
410
  await addCollectionImportsForTemplate(workspace.projectDir, resolvedTemplateId, result.variables);
403
- await appendBlockConfigEntries(workspace.projectDir, buildConfigEntries(resolvedTemplateId, result.variables), resolvedTemplateId === "persistence" ||
404
- (resolvedTemplateId === "compound" &&
405
- isCompoundPersistenceEnabled(result.variables)));
411
+ await appendBlockConfigEntries(workspace.projectDir, buildConfigEntries(resolvedTemplateId, result.variables), requiresPersistentIdentity);
406
412
  await syncWorkspaceAddedBlockArtifacts(workspace.projectDir, resolvedTemplateId, result.variables);
407
413
  await updateWorkspaceMigrationConfigIfPresent(workspace.projectDir, buildMigrationBlocks(resolvedTemplateId, result.variables));
408
414
  return {
409
415
  blockSlugs: collectWorkspaceBlockPaths(workspace.projectDir, resolvedTemplateId, result.variables).map((targetPath) => path.basename(targetPath)),
410
416
  projectDir: workspace.projectDir,
411
417
  templateId: resolvedTemplateId,
412
- warnings: result.warnings,
418
+ warnings: [
419
+ ...result.warnings,
420
+ ...(addedPersistentIdentityDependency
421
+ ? [
422
+ `Added @wordpress/data for persistent block identities. Run \`${formatInstallCommand(workspace.packageManager)}\` before building the workspace.`,
423
+ ]
424
+ : []),
425
+ ],
413
426
  };
414
427
  }
415
428
  catch (error) {
@@ -1,7 +1,7 @@
1
1
  import { promises as fsp } from 'node:fs';
2
2
  import path from 'node:path';
3
3
  import { pathExists, readOptionalUtf8File } from '../shared/fs-async.js';
4
- import { safeJsonParse } from '../shared/json-utils.js';
4
+ import { detectJsonIndent, safeJsonParse, } from '../shared/json-utils.js';
5
5
  import { appendWorkspaceInventoryEntries, readWorkspaceInventoryAsync, } from '../workspace/workspace-inventory.js';
6
6
  import { buildAdminViewConfigEntry, buildAdminViewConfigSource, buildAdminViewEntrySource, buildAdminViewPhpSource, buildAdminViewRegistrySource, buildAdminViewScreenSource, buildAdminViewStyleSource, buildAdminViewTypesSource, buildCoreDataAdminViewDataSource, buildCoreDataAdminViewScreenSource, buildDefaultAdminViewDataSource, buildRestAdminViewDataSource, buildRestSettingsAdminViewConfigSource, buildRestSettingsAdminViewDataSource, buildRestSettingsAdminViewScreenSource, buildRestSettingsAdminViewTypesSource, } from './cli-add-workspace-admin-view-templates.js';
7
7
  import { ADMIN_VIEWS_PHP_GLOB, isAdminViewCoreDataSource, isAdminViewManualSettingsRestResource, } from './cli-add-workspace-admin-view-types.js';
@@ -10,10 +10,6 @@ import { getWorkspaceBootstrapPath, patchFile, } from './cli-add-shared.js';
10
10
  import { appendPhpSnippetBeforeClosingTag, executeWorkspaceMutationPlan, insertPhpSnippetBeforeWorkspaceAnchors, } from './cli-add-workspace-mutation.js';
11
11
  import { DEFAULT_WORDPRESS_CORE_DATA_VERSION, DEFAULT_WORDPRESS_DATA_VERSION, DEFAULT_WORDPRESS_DATAVIEWS_VERSION, DEFAULT_WP_TYPIA_DATAVIEWS_VERSION, resolveManagedPackageVersionRange, } from '../shared/package-versions.js';
12
12
  import { findPhpFunctionRange, hasPhpFunctionDefinition, replacePhpFunctionDefinition, } from '../shared/php-utils.js';
13
- function detectJsonIndent(source) {
14
- const indentMatch = /\n([ \t]+)"/u.exec(source);
15
- return indentMatch?.[1] ?? 2;
16
- }
17
13
  const LEGACY_MANUAL_REST_API_SOURCE_PATTERN = /^\s*export\s+\*\s+from\s+["']\.\/api-client["'];?\s*$/u;
18
14
  const MANUAL_REST_API_DIRECT_CALL_EXPORT_PATTERN = /(?:^|\n)\s*export\s+(?:(?:async\s+)?function|const|let|var)\s+callManualRestContract\b/u;
19
15
  const MANUAL_REST_API_NAMED_EXPORT_PATTERN = /(?:^|\n)\s*export\s*\{([^}]*)\}/gu;
@@ -1,6 +1,7 @@
1
1
  import { promises as fsp } from "node:fs";
2
2
  import path from "node:path";
3
- import { assertValidHookAnchor, assertValidHookedBlockPosition, getMutableBlockHooks, normalizeBlockSlug, readWorkspaceBlockJson, resolveWorkspaceBlock, rollbackWorkspaceMutation, snapshotWorkspaceFiles, } from "./cli-add-shared.js";
3
+ import { executeWorkspaceMutationPlan } from "./cli-add-workspace-mutation.js";
4
+ import { assertValidHookAnchor, assertValidHookedBlockPosition, getMutableBlockHooks, normalizeBlockSlug, readWorkspaceBlockJson, resolveWorkspaceBlock, } from "./cli-add-shared.js";
4
5
  import { readWorkspaceInventoryAsync } from "../workspace/workspace-inventory.js";
5
6
  import { resolveWorkspaceProject } from "../workspace/workspace-project.js";
6
7
  /**
@@ -35,23 +36,17 @@ export async function runAddHookedBlockCommand({ anchorBlockName, blockName, cwd
35
36
  if (Object.prototype.hasOwnProperty.call(blockHooks, resolvedAnchorBlockName)) {
36
37
  throw new Error(`${blockJsonRelativePath} already defines a blockHooks entry for "${resolvedAnchorBlockName}".`);
37
38
  }
38
- const mutationSnapshot = {
39
- fileSources: await snapshotWorkspaceFiles([blockJsonPath]),
40
- snapshotDirs: [],
41
- targetPaths: [],
42
- };
43
- try {
44
- blockHooks[resolvedAnchorBlockName] = resolvedPosition;
45
- await fsp.writeFile(blockJsonPath, JSON.stringify(blockJson, null, "\t"), "utf8");
46
- return {
47
- anchorBlockName: resolvedAnchorBlockName,
48
- blockSlug,
49
- position: resolvedPosition,
50
- projectDir: workspace.projectDir,
51
- };
52
- }
53
- catch (error) {
54
- await rollbackWorkspaceMutation(mutationSnapshot);
55
- throw error;
56
- }
39
+ return executeWorkspaceMutationPlan({
40
+ filePaths: [blockJsonPath],
41
+ run: async () => {
42
+ blockHooks[resolvedAnchorBlockName] = resolvedPosition;
43
+ await fsp.writeFile(blockJsonPath, JSON.stringify(blockJson, null, "\t"), "utf8");
44
+ return {
45
+ anchorBlockName: resolvedAnchorBlockName,
46
+ blockSlug,
47
+ position: resolvedPosition,
48
+ projectDir: workspace.projectDir,
49
+ };
50
+ },
51
+ });
57
52
  }
@@ -1,10 +1,19 @@
1
- export interface WorkspaceMutationPlan<TResult> {
1
+ import { type WorkspaceMutationSnapshot } from "./cli-add-shared.js";
2
+ /**
3
+ * Paths captured before a workspace mutation so its filesystem changes can be rolled back.
4
+ */
5
+ export interface WorkspaceMutationSnapshotPlan {
2
6
  /** Files to capture before the mutation starts. Missing files are restored as absent. */
3
7
  filePaths: string[];
4
8
  /** Snapshot directories created by the mutation, usually migration fixtures. */
5
9
  snapshotDirs?: string[];
6
10
  /** Created files or directories to remove if the mutation fails. */
7
11
  targetPaths?: string[];
12
+ }
13
+ /**
14
+ * A workspace mutation snapshot plan paired with the operation to execute.
15
+ */
16
+ export interface WorkspaceMutationPlan<TResult> extends WorkspaceMutationSnapshotPlan {
8
17
  /** Mutating work to execute after the snapshot is captured. */
9
18
  run: () => Promise<TResult>;
10
19
  }
@@ -16,10 +25,14 @@ export declare class WorkspaceMutationRollbackError extends Error {
16
25
  readonly rollbackError: unknown;
17
26
  constructor(mutationError: unknown, rollbackError: unknown);
18
27
  }
28
+ /**
29
+ * Capture the files and paths needed to roll back a workspace add mutation.
30
+ */
31
+ export declare function createWorkspaceMutationSnapshot({ filePaths, snapshotDirs, targetPaths, }: WorkspaceMutationSnapshotPlan): Promise<WorkspaceMutationSnapshot>;
19
32
  /**
20
33
  * Execute a workspace add mutation with rollback on any failure.
21
34
  */
22
- export declare function executeWorkspaceMutationPlan<TResult>({ filePaths, run, snapshotDirs, targetPaths, }: WorkspaceMutationPlan<TResult>): Promise<TResult>;
35
+ export declare function executeWorkspaceMutationPlan<TResult>(plan: WorkspaceMutationPlan<TResult>): Promise<TResult>;
23
36
  /**
24
37
  * Insert a PHP snippet before the workspace textdomain hook or closing tag.
25
38
  */
@@ -15,16 +15,22 @@ export class WorkspaceMutationRollbackError extends Error {
15
15
  }
16
16
  }
17
17
  /**
18
- * Execute a workspace add mutation with rollback on any failure.
18
+ * Capture the files and paths needed to roll back a workspace add mutation.
19
19
  */
20
- export async function executeWorkspaceMutationPlan({ filePaths, run, snapshotDirs = [], targetPaths = [], }) {
21
- const mutationSnapshot = {
20
+ export async function createWorkspaceMutationSnapshot({ filePaths, snapshotDirs = [], targetPaths = [], }) {
21
+ return {
22
22
  fileSources: await snapshotWorkspaceFiles(filePaths),
23
23
  snapshotDirs: [...snapshotDirs],
24
24
  targetPaths: [...targetPaths],
25
25
  };
26
+ }
27
+ /**
28
+ * Execute a workspace add mutation with rollback on any failure.
29
+ */
30
+ export async function executeWorkspaceMutationPlan(plan) {
31
+ const mutationSnapshot = await createWorkspaceMutationSnapshot(plan);
26
32
  try {
27
- return await run();
33
+ return await plan.run();
28
34
  }
29
35
  catch (error) {
30
36
  try {
@@ -6,7 +6,8 @@ interface PersistenceTemplateVariablesLike {
6
6
  slugKebabCase: string;
7
7
  title: string;
8
8
  }
9
- interface SyncPersistenceRestArtifactsOptions {
9
+ /** Inputs used to plan and sync one persistence scaffold's REST artifacts. */
10
+ export interface SyncPersistenceRestArtifactsOptions {
10
11
  apiTypesFile: string;
11
12
  outputDir: string;
12
13
  projectDir: string;
@@ -20,21 +21,21 @@ interface SyncPersistenceRestArtifactsOptions {
20
21
  * @returns Endpoint manifest covering bootstrap, state read, and state write operations.
21
22
  */
22
23
  export declare function buildPersistenceEndpointManifest(variables: PersistenceTemplateVariablesLike): import("@wp-typia/block-runtime/metadata-core").EndpointManifestDefinition<{
24
+ readonly "state-query": {
25
+ readonly sourceTypeName: `${string}StateQuery`;
26
+ };
23
27
  readonly "bootstrap-query": {
24
28
  readonly sourceTypeName: `${string}BootstrapQuery`;
25
29
  };
30
+ readonly "write-state-request": {
31
+ readonly sourceTypeName: `${string}WriteStateRequest`;
32
+ };
26
33
  readonly "bootstrap-response": {
27
34
  readonly sourceTypeName: `${string}BootstrapResponse`;
28
35
  };
29
- readonly "state-query": {
30
- readonly sourceTypeName: `${string}StateQuery`;
31
- };
32
36
  readonly "state-response": {
33
37
  readonly sourceTypeName: `${string}StateResponse`;
34
38
  };
35
- readonly "write-state-request": {
36
- readonly sourceTypeName: `${string}WriteStateRequest`;
37
- };
38
39
  }, readonly [{
39
40
  readonly auth: "public";
40
41
  readonly method: "GET";
@@ -66,6 +67,30 @@ export declare function buildPersistenceEndpointManifest(variables: PersistenceT
66
67
  readonly summary: "Read fresh session bootstrap state for the current viewer.";
67
68
  readonly tags: readonly [string];
68
69
  }]>;
70
+ /** Output paths and metadata for one persistence REST contract schema. */
71
+ export interface PersistenceRestArtifactSchemaPlan {
72
+ jsonSchemaFile: string;
73
+ openApiFile: string;
74
+ openApiInfo: {
75
+ title: string;
76
+ version: string;
77
+ };
78
+ sourceTypeName: string;
79
+ }
80
+ /** Complete compiler-derived output plan for one persistence REST surface. */
81
+ export interface PersistenceRestArtifactPlan {
82
+ clientFile: string;
83
+ manifest: ReturnType<typeof buildPersistenceEndpointManifest>;
84
+ openApiFile: string;
85
+ schemas: PersistenceRestArtifactSchemaPlan[];
86
+ }
87
+ /**
88
+ * Build the canonical output plan shared by scaffold generation and previews.
89
+ *
90
+ * @param options Scaffold output paths plus persistence template variables.
91
+ * @returns REST schema, aggregate OpenAPI, and client output paths.
92
+ */
93
+ export declare function buildPersistenceRestArtifactPlan(options: SyncPersistenceRestArtifactsOptions): PersistenceRestArtifactPlan;
69
94
  /**
70
95
  * Generate the REST-derived persistence artifacts for a scaffolded block.
71
96
  *
@@ -10,21 +10,21 @@ import { defineEndpointManifest, syncEndpointClient, syncRestOpenApi, syncTypeSc
10
10
  export function buildPersistenceEndpointManifest(variables) {
11
11
  return defineEndpointManifest({
12
12
  contracts: {
13
+ "state-query": {
14
+ sourceTypeName: `${variables.pascalCase}StateQuery`,
15
+ },
13
16
  "bootstrap-query": {
14
17
  sourceTypeName: `${variables.pascalCase}BootstrapQuery`,
15
18
  },
19
+ "write-state-request": {
20
+ sourceTypeName: `${variables.pascalCase}WriteStateRequest`,
21
+ },
16
22
  "bootstrap-response": {
17
23
  sourceTypeName: `${variables.pascalCase}BootstrapResponse`,
18
24
  },
19
- "state-query": {
20
- sourceTypeName: `${variables.pascalCase}StateQuery`,
21
- },
22
25
  "state-response": {
23
26
  sourceTypeName: `${variables.pascalCase}StateResponse`,
24
27
  },
25
- "write-state-request": {
26
- sourceTypeName: `${variables.pascalCase}WriteStateRequest`,
27
- },
28
28
  },
29
29
  endpoints: [
30
30
  {
@@ -67,6 +67,29 @@ export function buildPersistenceEndpointManifest(variables) {
67
67
  },
68
68
  });
69
69
  }
70
+ /**
71
+ * Build the canonical output plan shared by scaffold generation and previews.
72
+ *
73
+ * @param options Scaffold output paths plus persistence template variables.
74
+ * @returns REST schema, aggregate OpenAPI, and client output paths.
75
+ */
76
+ export function buildPersistenceRestArtifactPlan(options) {
77
+ const manifest = buildPersistenceEndpointManifest(options.variables);
78
+ return {
79
+ clientFile: path.join(options.outputDir, "api-client.ts"),
80
+ manifest,
81
+ openApiFile: path.join(options.outputDir, "api.openapi.json"),
82
+ schemas: Object.entries(manifest.contracts).map(([baseName, contract]) => ({
83
+ jsonSchemaFile: path.join(options.outputDir, "api-schemas", `${baseName}.schema.json`),
84
+ openApiFile: path.join(options.outputDir, "api-schemas", `${baseName}.openapi.json`),
85
+ openApiInfo: {
86
+ title: contract.sourceTypeName,
87
+ version: "1.0.0",
88
+ },
89
+ sourceTypeName: contract.sourceTypeName,
90
+ })),
91
+ };
92
+ }
70
93
  /**
71
94
  * Generate the REST-derived persistence artifacts for a scaffolded block.
72
95
  *
@@ -74,25 +97,28 @@ export function buildPersistenceEndpointManifest(variables) {
74
97
  * @returns A promise that resolves after schema, OpenAPI, and client files are written.
75
98
  */
76
99
  export async function syncPersistenceRestArtifacts({ apiTypesFile, outputDir, projectDir, variables, }) {
77
- const manifest = buildPersistenceEndpointManifest(variables);
78
- for (const [baseName, contract] of Object.entries(manifest.contracts)) {
100
+ const plan = buildPersistenceRestArtifactPlan({
101
+ apiTypesFile,
102
+ outputDir,
103
+ projectDir,
104
+ variables,
105
+ });
106
+ for (const schema of plan.schemas) {
79
107
  await syncTypeSchemas({
80
- jsonSchemaFile: path.join(outputDir, "api-schemas", `${baseName}.schema.json`),
81
- openApiFile: path.join(outputDir, "api-schemas", `${baseName}.openapi.json`),
108
+ ...schema,
82
109
  projectRoot: projectDir,
83
- sourceTypeName: contract.sourceTypeName,
84
110
  typesFile: apiTypesFile,
85
111
  });
86
112
  }
87
113
  await syncRestOpenApi({
88
- manifest,
89
- openApiFile: path.join(outputDir, "api.openapi.json"),
114
+ manifest: plan.manifest,
115
+ openApiFile: plan.openApiFile,
90
116
  projectRoot: projectDir,
91
117
  typesFile: apiTypesFile,
92
118
  });
93
119
  await syncEndpointClient({
94
- clientFile: path.join(outputDir, "api-client.ts"),
95
- manifest,
120
+ clientFile: plan.clientFile,
121
+ manifest: plan.manifest,
96
122
  projectRoot: projectDir,
97
123
  typesFile: apiTypesFile,
98
124
  });
@@ -4,6 +4,7 @@
4
4
  export interface CliDiagnosticMessage {
5
5
  code: CliDiagnosticCode;
6
6
  command: string;
7
+ data?: Record<string, unknown>;
7
8
  detailLines: string[];
8
9
  summary: string;
9
10
  }
@@ -12,6 +13,7 @@ export declare const CLI_DIAGNOSTIC_CODES: {
12
13
  readonly CONFIGURATION_MISSING: "configuration-missing";
13
14
  readonly DEPENDENCIES_NOT_INSTALLED: "dependencies-not-installed";
14
15
  readonly DOCTOR_CHECK_FAILED: "doctor-check-failed";
16
+ readonly GENERATED_ARTIFACT_DRIFT: "generated-artifact-drift";
15
17
  readonly INVALID_ARGUMENT: "invalid-argument";
16
18
  readonly INVALID_COMMAND: "invalid-command";
17
19
  readonly MISSING_ARGUMENT: "missing-argument";
@@ -52,6 +54,10 @@ export declare const CLI_DIAGNOSTIC_CODE_METADATA: {
52
54
  cause: string;
53
55
  recovery: string;
54
56
  };
57
+ "generated-artifact-drift": {
58
+ cause: string;
59
+ recovery: string;
60
+ };
55
61
  "invalid-argument": {
56
62
  cause: string;
57
63
  recovery: string;
@@ -104,6 +110,7 @@ type DoctorCheckLike = {
104
110
  export declare class CliDiagnosticError extends Error {
105
111
  readonly code: CliDiagnosticCode;
106
112
  readonly command: string;
113
+ readonly data?: Record<string, unknown>;
107
114
  readonly detailLines: string[];
108
115
  readonly summary: string;
109
116
  constructor(message: CliDiagnosticMessage, options?: ErrorOptions);
@@ -130,6 +137,7 @@ export declare function createCliDiagnosticCodeError<TCode extends CliDiagnostic
130
137
  export declare function createCliCommandError(options: {
131
138
  command: string;
132
139
  code?: CliDiagnosticCode;
140
+ data?: Record<string, unknown>;
133
141
  detailLines?: string[];
134
142
  error?: unknown;
135
143
  summary?: string;
@@ -142,6 +150,7 @@ export declare function formatCliDiagnosticError(error: unknown): string;
142
150
  export declare function serializeCliDiagnosticError(error: unknown): {
143
151
  code: CliDiagnosticCode;
144
152
  command?: string;
153
+ data?: Record<string, unknown>;
145
154
  detailLines?: string[];
146
155
  kind: "command-execution";
147
156
  message: string;
@@ -3,6 +3,7 @@ export const CLI_DIAGNOSTIC_CODES = {
3
3
  CONFIGURATION_MISSING: "configuration-missing",
4
4
  DEPENDENCIES_NOT_INSTALLED: "dependencies-not-installed",
5
5
  DOCTOR_CHECK_FAILED: "doctor-check-failed",
6
+ GENERATED_ARTIFACT_DRIFT: "generated-artifact-drift",
6
7
  INVALID_ARGUMENT: "invalid-argument",
7
8
  INVALID_COMMAND: "invalid-command",
8
9
  MISSING_ARGUMENT: "missing-argument",
@@ -33,6 +34,10 @@ export const CLI_DIAGNOSTIC_CODE_METADATA = {
33
34
  cause: "One or more doctor checks reported a failing environment or workspace row.",
34
35
  recovery: "Inspect the failed check labels and details, fix the reported drift or missing prerequisite, then rerun `wp-typia doctor`.",
35
36
  },
37
+ [CLI_DIAGNOSTIC_CODES.GENERATED_ARTIFACT_DRIFT]: {
38
+ cause: "One or more generated project artifacts are missing or differ from the TypeScript source of truth.",
39
+ recovery: "Run the reported project sync command to regenerate artifacts, then rerun `wp-typia sync --check`.",
40
+ },
36
41
  [CLI_DIAGNOSTIC_CODES.INVALID_ARGUMENT]: {
37
42
  cause: "An argument was present but did not match the supported value, shape, or project state.",
38
43
  recovery: "Correct the argument value using command help or the detail lines, then rerun the command.",
@@ -182,6 +187,7 @@ export class CliDiagnosticError extends Error {
182
187
  super(formatCliDiagnosticBlock(message), options);
183
188
  this.code = message.code;
184
189
  this.command = message.command;
190
+ this.data = message.data ? { ...message.data } : undefined;
185
191
  this.detailLines = [...message.detailLines];
186
192
  this.name = "CliDiagnosticError";
187
193
  this.summary = message.summary;
@@ -301,6 +307,7 @@ export function createCliCommandError(options) {
301
307
  return new CliDiagnosticError({
302
308
  code,
303
309
  command: options.command,
310
+ ...(options.data ? { data: options.data } : {}),
304
311
  detailLines,
305
312
  summary,
306
313
  }, options.error instanceof Error ? { cause: options.error } : undefined);
@@ -325,6 +332,7 @@ export function serializeCliDiagnosticError(error) {
325
332
  return {
326
333
  code: error.code,
327
334
  command: error.command,
335
+ ...(error.data ? { data: { ...error.data } } : {}),
328
336
  detailLines: [...error.detailLines],
329
337
  kind: "command-execution",
330
338
  message: formatCliDiagnosticBlock({
@@ -2,7 +2,7 @@ import fs from "node:fs";
2
2
  import path from "node:path";
3
3
  import { CLI_DIAGNOSTIC_CODES, createCliDiagnosticCodeError, } from "./cli-diagnostics.js";
4
4
  import { getPackageManager, transformPackageManagerText, } from "../shared/package-managers.js";
5
- import { getPackageVersions } from "../shared/package-versions.js";
5
+ import { DEFAULT_WORDPRESS_BLOCKS_TYPES_VERSION, DEFAULT_WORDPRESS_BLOCKS_VERSION, getPackageVersions, } from "../shared/package-versions.js";
6
6
  import { readJsonFileSync } from "../shared/json-utils.js";
7
7
  import { parseWorkspacePackageManagerId } from "../workspace/workspace-project.js";
8
8
  const BASE_RETROFIT_SCRIPTS = {
@@ -12,6 +12,8 @@ const BASE_RETROFIT_SCRIPTS = {
12
12
  };
13
13
  const BASE_RETROFIT_DEV_DEPENDENCIES = [
14
14
  "@typia/unplugin",
15
+ "@types/wordpress__blocks",
16
+ "@wordpress/blocks",
15
17
  "@wp-typia/block-runtime",
16
18
  "@wp-typia/block-types",
17
19
  "tsx",
@@ -72,6 +74,8 @@ function buildRequiredDevDependencyMap() {
72
74
  const versions = getPackageVersions();
73
75
  return {
74
76
  "@typia/unplugin": versions.typiaUnpluginPackageVersion,
77
+ "@types/wordpress__blocks": DEFAULT_WORDPRESS_BLOCKS_TYPES_VERSION,
78
+ "@wordpress/blocks": DEFAULT_WORDPRESS_BLOCKS_VERSION,
75
79
  "@wp-typia/block-runtime": versions.blockRuntimePackageVersion,
76
80
  "@wp-typia/block-types": versions.blockTypesPackageVersion,
77
81
  tsx: versions.tsxPackageVersion,
@@ -0,0 +1,38 @@
1
+ import ts from 'typescript';
2
+ export interface CanonicalRuntimeImportShape {
3
+ defaultBinding?: string;
4
+ namedBindings?: ReadonlySet<string>;
5
+ }
6
+ /**
7
+ * Check whether a generated sync helper statement is inert until main() runs.
8
+ * Runtime imports are limited to the modules used by that helper; all other
9
+ * imports must be erased by TypeScript.
10
+ */
11
+ export declare function isAllowedSyncHelperTopLevelStatement(statement: ts.Statement, allowedRuntimeImportModules: ReadonlySet<string>, isAllowedVariableStatement?: (statement: ts.VariableStatement) => boolean): boolean;
12
+ /** Require runtime imports to match the generated helper's load-time API. */
13
+ export declare function hasCanonicalRuntimeImports(sourceFile: ts.SourceFile, expectedImports: ReadonlyMap<string, CanonicalRuntimeImportShape>): boolean;
14
+ /** Find a control-flow completion without crossing a nested function boundary. */
15
+ export declare function containsCompletion(node: ts.Node, isTerminal: (candidate: ts.Node) => boolean): boolean;
16
+ /** Unwrap supported static TypeScript wrappers to reach the underlying expression. */
17
+ export declare function unwrapStaticExpression(expression: ts.Expression): ts.Expression;
18
+ /** Validate the generated main().catch() failure boundary. */
19
+ export declare function isCanonicalSyncMainCatchHandler(rawHandler: ts.Expression): boolean;
20
+ /** Return a side-effect-free `new Error(message)` expression when present. */
21
+ export declare function getSafeErrorConstruction(rawExpression: ts.Expression): ts.NewExpression | null;
22
+ /** Validate a side-effect-free `new Error(message)` expression. */
23
+ export declare function isSafeErrorConstruction(rawExpression: ts.Expression): boolean;
24
+ /** Require the generated parser's terminal unknown-flag failure. */
25
+ export declare function hasCanonicalUnknownFlagThrow(body: ts.Statement, argumentBinding: string): boolean;
26
+ /**
27
+ * Reject executable parser work beyond the generated option assignments.
28
+ * Parser bodies are inspected rather than executed by doctor, so accepting an
29
+ * unrelated call or mutation here would let the real sync command diverge from
30
+ * the replayed options.
31
+ */
32
+ export declare function hasOnlyCanonicalParserEffects(node: ts.Node, optionsBinding: string, indexBinding?: string | null): boolean;
33
+ /** Count continues that target the generated parser's outer argument loop. */
34
+ export declare function countOuterParserContinues(node: ts.Node): number;
35
+ /** Detect a static process.exit() call, regardless of its exit code. */
36
+ export declare function isProcessExitCompletion(node: ts.Node): boolean;
37
+ /** Check whether a prior statement can terminate the current function. */
38
+ export declare function hasEarlierAbruptCompletion(statements: readonly ts.Statement[], statementIndex: number): boolean;