wp-typia 0.22.0 → 0.22.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.
@@ -4,6 +4,7 @@ import {
4
4
  REQUIRED_WORKSPACE_ABILITY_COMPATIBILITY,
5
5
  assertExternalLayerCompositionOptions,
6
6
  copyInterpolatedDirectory,
7
+ createScaffoldCompatibilityConfig,
7
8
  getDefaultAnswers,
8
9
  listInterpolatedDirectoryOutputs,
9
10
  normalizeOptionalCliString,
@@ -20,7 +21,7 @@ import {
20
21
  scaffoldProject,
21
22
  syncPersistenceRestArtifacts,
22
23
  updatePluginHeaderCompatibility
23
- } from "./cli-1w5vkye4.js";
24
+ } from "./cli-kww2sraq.js";
24
25
  import {
25
26
  DEFAULT_WORDPRESS_ABILITIES_VERSION,
26
27
  DEFAULT_WORDPRESS_CORE_ABILITIES_VERSION,
@@ -28,11 +29,12 @@ import {
28
29
  DEFAULT_WORDPRESS_DATAVIEWS_VERSION,
29
30
  DEFAULT_WORDPRESS_DATA_VERSION,
30
31
  DEFAULT_WP_TYPIA_DATAVIEWS_VERSION,
31
- getPackageVersions
32
- } from "./cli-39er8888.js";
32
+ getPackageVersions,
33
+ resolveManagedPackageVersionRange
34
+ } from "./cli-1sm60g1z.js";
33
35
  import {
34
36
  snapshotProjectVersion
35
- } from "./cli-e623rs7g.js";
37
+ } from "./cli-6hcbjvym.js";
36
38
  import {
37
39
  ensureMigrationDirectories,
38
40
  parseMigrationConfig,
@@ -45,7 +47,6 @@ import {
45
47
  } from "./cli-sj5mtyzj.js";
46
48
  import"./cli-10pe4mf8.js";
47
49
  import {
48
- PROJECT_TOOLS_PACKAGE_ROOT,
49
50
  SHARED_WORKSPACE_TEMPLATE_ROOT
50
51
  } from "./cli-tke8twkn.js";
51
52
  import {
@@ -89,11 +90,12 @@ import {
89
90
  resolveWorkspaceBlock,
90
91
  rollbackWorkspaceMutation,
91
92
  snapshotWorkspaceFiles,
93
+ toCamelCase,
92
94
  toKebabCase,
93
95
  toPascalCase,
94
96
  toSnakeCase,
95
97
  toTitleCase
96
- } from "./cli-j180bk07.js";
98
+ } from "./cli-smzkbfna.js";
97
99
  import {
98
100
  createManagedTempRoot
99
101
  } from "./cli-t73q5aqz.js";
@@ -781,16 +783,15 @@ async function runAddBlockCommand({
781
783
  // ../wp-typia-project-tools/src/runtime/cli-add-workspace.ts
782
784
  import fs6 from "fs";
783
785
  import { promises as fsp9 } from "fs";
784
- import path13 from "path";
786
+ import path14 from "path";
785
787
 
786
- // ../wp-typia-project-tools/src/runtime/cli-add-workspace-admin-view.ts
787
- import fs3 from "fs";
788
- import { promises as fsp3 } from "fs";
789
- import { createRequire } from "module";
790
- import path4 from "path";
788
+ // ../wp-typia-project-tools/src/runtime/cli-add-workspace-admin-view-types.ts
791
789
  var ADMIN_VIEW_REST_SOURCE_KIND = "rest-resource";
792
790
  var ADMIN_VIEW_CORE_DATA_SOURCE_KIND = "core-data";
793
- var ADMIN_VIEW_CORE_DATA_ENTITY_KIND_IDS = ["postType", "taxonomy"];
791
+ var ADMIN_VIEW_CORE_DATA_ENTITY_KIND_IDS = [
792
+ "postType",
793
+ "taxonomy"
794
+ ];
794
795
  var ADMIN_VIEW_CORE_DATA_ENTITY_SEGMENT_PATTERN = /^[A-Za-z][A-Za-z0-9_-]*$/u;
795
796
  var ADMIN_VIEW_CORE_DATA_ENTITY_NAME_PATTERN = /^[a-z0-9][a-z0-9_-]*$/u;
796
797
  var ADMIN_VIEW_SOURCE_USAGE = "wp-typia add admin-view <name> --source <rest-resource:slug|core-data:kind/name>";
@@ -801,28 +802,20 @@ var ADMIN_VIEWS_STYLE_RTL = "build/admin-views/style-index-rtl.css";
801
802
  var ADMIN_VIEWS_PHP_GLOB = "/inc/admin-views/*.php";
802
803
  var ADMIN_VIEW_ALLOW_UNPUBLISHED_DATAVIEWS_ENV = "WP_TYPIA_ALLOW_UNPUBLISHED_DATAVIEWS";
803
804
  var ADMIN_VIEW_PUBLIC_INSTALLS_ENABLED = false;
804
- var require2 = createRequire(import.meta.url);
805
- function toCamelCase(input) {
806
- const pascalCase = toPascalCase(input);
807
- return `${pascalCase.charAt(0).toLowerCase()}${pascalCase.slice(1)}`;
805
+ function isAdminViewCoreDataSource(source) {
806
+ return source?.kind === ADMIN_VIEW_CORE_DATA_SOURCE_KIND;
808
807
  }
809
- function normalizeVersionRange(value, fallback) {
810
- const trimmed = value?.trim();
811
- if (!trimmed || trimmed.startsWith("workspace:")) {
812
- return fallback;
813
- }
814
- return /^[~^<>=]/u.test(trimmed) ? trimmed : `^${trimmed}`;
808
+ function isAdminViewRestResourceSource(source) {
809
+ return source?.kind === ADMIN_VIEW_REST_SOURCE_KIND;
815
810
  }
816
- function readPackageManifest(packageJsonPath) {
817
- try {
818
- return JSON.parse(fs3.readFileSync(packageJsonPath, "utf8"));
819
- } catch {
820
- return;
811
+ function formatAdminViewSourceLocator(source) {
812
+ if (isAdminViewCoreDataSource(source)) {
813
+ return `${source.kind}:${source.entityKind}/${source.entityName}`;
821
814
  }
815
+ return `${source.kind}:${source.slug}`;
822
816
  }
823
- function readPackageManifestVersion(packageJsonPath) {
824
- return readPackageManifest(packageJsonPath)?.version;
825
- }
817
+
818
+ // ../wp-typia-project-tools/src/runtime/cli-add-workspace-admin-view-source.ts
826
819
  function isAdminViewUnpublishedDataViewsOverrideEnabled() {
827
820
  return process.env[ADMIN_VIEW_ALLOW_UNPUBLISHED_DATAVIEWS_ENV]?.trim() === "1";
828
821
  }
@@ -832,36 +825,6 @@ function assertAdminViewPackageAvailability() {
832
825
  }
833
826
  throw createCliDiagnosticCodeError(CLI_DIAGNOSTIC_CODES.INVALID_ARGUMENT, "`wp-typia add admin-view` is temporarily unavailable because `@wp-typia/dataviews` is not published to npm for public installs yet.");
834
827
  }
835
- function detectJsonIndent(source) {
836
- const indentMatch = /\n([ \t]+)"/u.exec(source);
837
- return indentMatch?.[1] ?? 2;
838
- }
839
- function resolvePackageVersionRange(packageName, fallback, workspacePackageDirName) {
840
- if (workspacePackageDirName) {
841
- const workspaceVersion = readPackageManifestVersion(path4.join(PROJECT_TOOLS_PACKAGE_ROOT, "..", workspacePackageDirName, "package.json"));
842
- if (workspaceVersion) {
843
- return normalizeVersionRange(workspaceVersion, fallback);
844
- }
845
- }
846
- try {
847
- return normalizeVersionRange(readPackageManifestVersion(require2.resolve(`${packageName}/package.json`)), fallback);
848
- } catch {
849
- return fallback;
850
- }
851
- }
852
- function getAdminViewRelativeModuleSpecifier(adminViewSlug, workspaceFile) {
853
- const adminViewDir = `src/admin-views/${adminViewSlug}`;
854
- const normalizedFile = workspaceFile.replace(/\\/gu, "/");
855
- const modulePath = normalizedFile.replace(/\.[cm]?[jt]sx?$/u, "");
856
- const relativeModulePath = path4.posix.relative(adminViewDir, modulePath);
857
- return relativeModulePath.startsWith(".") ? relativeModulePath : `./${relativeModulePath}`;
858
- }
859
- function isAdminViewCoreDataSource(source) {
860
- return source?.kind === ADMIN_VIEW_CORE_DATA_SOURCE_KIND;
861
- }
862
- function isAdminViewRestResourceSource(source) {
863
- return source?.kind === ADMIN_VIEW_REST_SOURCE_KIND;
864
- }
865
828
  function assertValidCoreDataEntitySegment(label, value) {
866
829
  const trimmed = value.trim();
867
830
  if (!trimmed) {
@@ -889,12 +852,6 @@ function assertValidCoreDataEntityKind(value) {
889
852
  }
890
853
  return normalized;
891
854
  }
892
- function formatAdminViewSourceLocator(source) {
893
- if (isAdminViewCoreDataSource(source)) {
894
- return `${source.kind}:${source.entityKind}/${source.entityName}`;
895
- }
896
- return `${source.kind}:${source.slug}`;
897
- }
898
855
  function parseAdminViewSource(source) {
899
856
  const trimmed = source?.trim();
900
857
  if (!trimmed) {
@@ -938,6 +895,24 @@ function resolveRestResourceSource(restResources, source) {
938
895
  }
939
896
  return restResource;
940
897
  }
898
+ function resolveAdminViewCoreDataSource(source) {
899
+ return isAdminViewCoreDataSource(source) ? source : undefined;
900
+ }
901
+
902
+ // ../wp-typia-project-tools/src/runtime/cli-add-workspace-admin-view-scaffold.ts
903
+ import fs3 from "fs";
904
+ import { promises as fsp3 } from "fs";
905
+ import path5 from "path";
906
+
907
+ // ../wp-typia-project-tools/src/runtime/cli-add-workspace-admin-view-templates.ts
908
+ import path4 from "path";
909
+ function getAdminViewRelativeModuleSpecifier(adminViewSlug, workspaceFile) {
910
+ const adminViewDir = `src/admin-views/${adminViewSlug}`;
911
+ const normalizedFile = workspaceFile.replace(/\\/gu, "/");
912
+ const modulePath = normalizedFile.replace(/\.[cm]?[jt]sx?$/u, "");
913
+ const relativeModulePath = path4.posix.relative(adminViewDir, modulePath);
914
+ return relativeModulePath.startsWith(".") ? relativeModulePath : `./${relativeModulePath}`;
915
+ }
941
916
  function buildAdminViewConfigEntry(adminViewSlug, source) {
942
917
  return [
943
918
  "\t{",
@@ -1070,8 +1045,8 @@ function buildAdminViewConfigSource(adminViewSlug, textDomain, source, restResou
1070
1045
  const camelName = toCamelCase(adminViewSlug);
1071
1046
  const itemTypeName = `${pascalName}AdminViewItem`;
1072
1047
  const dataViewsName = `${camelName}AdminDataViews`;
1073
- const isCoreDataSource = source?.kind === ADMIN_VIEW_CORE_DATA_SOURCE_KIND;
1074
- const isTaxonomyCoreDataSource = source?.kind === ADMIN_VIEW_CORE_DATA_SOURCE_KIND && source.entityKind === "taxonomy";
1048
+ const isCoreDataSource = isAdminViewCoreDataSource(source);
1049
+ const isTaxonomyCoreDataSource = isAdminViewCoreDataSource(source) && source.entityKind === "taxonomy";
1075
1050
  const defaultViewFields = restResource ? "['id']" : isTaxonomyCoreDataSource ? "['name', 'slug', 'count']" : isCoreDataSource ? "['title', 'slug', 'status', 'updatedAt']" : "['title', 'status', 'updatedAt']";
1076
1051
  const searchEnabled = restResource ? "false" : "true";
1077
1052
  const titleFieldSource = restResource ? "" : isTaxonomyCoreDataSource ? ` titleField: 'name',
@@ -1887,12 +1862,31 @@ add_action( 'admin_menu', '${registerFunctionName}' );
1887
1862
  add_action( 'admin_enqueue_scripts', '${enqueueFunctionName}' );
1888
1863
  `;
1889
1864
  }
1865
+
1866
+ // ../wp-typia-project-tools/src/runtime/cli-add-workspace-admin-view-scaffold.ts
1867
+ function detectJsonIndent(source) {
1868
+ const indentMatch = /\n([ \t]+)"/u.exec(source);
1869
+ return indentMatch?.[1] ?? 2;
1870
+ }
1890
1871
  async function ensureAdminViewPackageDependencies(workspace, adminViewSource) {
1891
- const packageJsonPath = path4.join(workspace.projectDir, "package.json");
1892
- const wpTypiaDataViewsVersion = resolvePackageVersionRange("@wp-typia/dataviews", DEFAULT_WP_TYPIA_DATAVIEWS_VERSION, "wp-typia-dataviews");
1893
- const wordpressDataViewsVersion = resolvePackageVersionRange("@wordpress/dataviews", DEFAULT_WORDPRESS_DATAVIEWS_VERSION);
1894
- const wordpressCoreDataVersion = resolvePackageVersionRange("@wordpress/core-data", DEFAULT_WORDPRESS_CORE_DATA_VERSION);
1895
- const wordpressDataVersion = resolvePackageVersionRange("@wordpress/data", DEFAULT_WORDPRESS_DATA_VERSION);
1872
+ const packageJsonPath = path5.join(workspace.projectDir, "package.json");
1873
+ const wpTypiaDataViewsVersion = resolveManagedPackageVersionRange({
1874
+ fallback: DEFAULT_WP_TYPIA_DATAVIEWS_VERSION,
1875
+ packageName: "@wp-typia/dataviews",
1876
+ workspacePackageDirName: "wp-typia-dataviews"
1877
+ });
1878
+ const wordpressDataViewsVersion = resolveManagedPackageVersionRange({
1879
+ fallback: DEFAULT_WORDPRESS_DATAVIEWS_VERSION,
1880
+ packageName: "@wordpress/dataviews"
1881
+ });
1882
+ const wordpressCoreDataVersion = resolveManagedPackageVersionRange({
1883
+ fallback: DEFAULT_WORDPRESS_CORE_DATA_VERSION,
1884
+ packageName: "@wordpress/core-data"
1885
+ });
1886
+ const wordpressDataVersion = resolveManagedPackageVersionRange({
1887
+ fallback: DEFAULT_WORDPRESS_DATA_VERSION,
1888
+ packageName: "@wordpress/data"
1889
+ });
1896
1890
  await patchFile(packageJsonPath, (source) => {
1897
1891
  const packageJson = JSON.parse(source);
1898
1892
  const coreDataDependencies = isAdminViewCoreDataSource(adminViewSource) ? {
@@ -1923,6 +1917,7 @@ async function ensureAdminViewBootstrapAnchors(workspace) {
1923
1917
  let nextSource = source;
1924
1918
  const loadFunctionName = `${workspace.workspace.phpPrefix}_load_admin_views`;
1925
1919
  const loadHook = `add_action( 'plugins_loaded', '${loadFunctionName}' );`;
1920
+ const loadHookPattern = new RegExp(`add_action\\(\\s*['"]plugins_loaded['"]\\s*,\\s*['"]${loadFunctionName}['"]\\s*\\)\\s*;`, "u");
1926
1921
  const loadFunction = `
1927
1922
 
1928
1923
  function ${loadFunctionName}() {
@@ -1967,19 +1962,19 @@ ${snippet}
1967
1962
  if (!functionSource.includes(ADMIN_VIEWS_PHP_GLOB)) {
1968
1963
  const replacedSource = replacePhpFunctionDefinition(nextSource, loadFunctionName, loadFunction);
1969
1964
  if (!replacedSource) {
1970
- throw new Error(`Unable to repair ${path4.basename(bootstrapPath)} for ${loadFunctionName}.`);
1965
+ throw new Error(`Unable to repair ${path5.basename(bootstrapPath)} for ${loadFunctionName}.`);
1971
1966
  }
1972
1967
  nextSource = replacedSource;
1973
1968
  }
1974
1969
  }
1975
- if (!nextSource.includes(loadHook)) {
1970
+ if (!loadHookPattern.test(nextSource)) {
1976
1971
  appendPhpSnippet(loadHook);
1977
1972
  }
1978
1973
  return nextSource;
1979
1974
  });
1980
1975
  }
1981
1976
  async function ensureAdminViewBuildScriptAnchors(workspace) {
1982
- const buildScriptPath = path4.join(workspace.projectDir, "scripts", "build-workspace.mjs");
1977
+ const buildScriptPath = path5.join(workspace.projectDir, "scripts", "build-workspace.mjs");
1983
1978
  await patchFile(buildScriptPath, (source) => {
1984
1979
  if (/['"]src\/admin-views\/index\.(?:ts|js)['"]/u.test(source)) {
1985
1980
  return source;
@@ -2003,11 +1998,11 @@ async function ensureAdminViewBuildScriptAnchors(workspace) {
2003
1998
  if (nextSource !== source) {
2004
1999
  return nextSource;
2005
2000
  }
2006
- throw new Error(`Unable to update ${path4.relative(workspace.projectDir, buildScriptPath)} for admin view shared entries.`);
2001
+ throw new Error(`Unable to update ${path5.relative(workspace.projectDir, buildScriptPath)} for admin view shared entries.`);
2007
2002
  });
2008
2003
  }
2009
2004
  async function ensureAdminViewWebpackAnchors(workspace) {
2010
- const webpackConfigPath = path4.join(workspace.projectDir, "webpack.config.js");
2005
+ const webpackConfigPath = path5.join(workspace.projectDir, "webpack.config.js");
2011
2006
  await patchFile(webpackConfigPath, (source) => {
2012
2007
  if (/['"]admin-views\/index['"]/u.test(source)) {
2013
2008
  return source;
@@ -2048,17 +2043,17 @@ async function ensureAdminViewWebpackAnchors(workspace) {
2048
2043
  }`;
2049
2044
  nextSource = source.replace(legacySharedEntriesBlockPattern, nextSharedEntriesBlock);
2050
2045
  if (nextSource === source) {
2051
- throw new Error(`Unable to update ${path4.relative(workspace.projectDir, webpackConfigPath)} for admin view shared entries.`);
2046
+ throw new Error(`Unable to update ${path5.relative(workspace.projectDir, webpackConfigPath)} for admin view shared entries.`);
2052
2047
  }
2053
2048
  return nextSource;
2054
2049
  });
2055
2050
  }
2056
2051
  function resolveAdminViewRegistryPath(projectDir) {
2057
- const adminViewsDir = path4.join(projectDir, "src", "admin-views");
2052
+ const adminViewsDir = path5.join(projectDir, "src", "admin-views");
2058
2053
  return [
2059
- path4.join(adminViewsDir, "index.ts"),
2060
- path4.join(adminViewsDir, "index.js")
2061
- ].find((candidatePath) => fs3.existsSync(candidatePath)) ?? path4.join(adminViewsDir, "index.ts");
2054
+ path5.join(adminViewsDir, "index.ts"),
2055
+ path5.join(adminViewsDir, "index.js")
2056
+ ].find((candidatePath) => fs3.existsSync(candidatePath)) ?? path5.join(adminViewsDir, "index.ts");
2062
2057
  }
2063
2058
  function readAdminViewRegistrySlugs(registryPath) {
2064
2059
  if (!fs3.existsSync(registryPath)) {
@@ -2068,35 +2063,34 @@ function readAdminViewRegistrySlugs(registryPath) {
2068
2063
  return Array.from(source.matchAll(/^\s*import\s+['"]\.\/([^/'"]+)(?:\/index(?:\.[cm]?[jt]sx?)?)?['"];?\s*$/gmu)).map((match) => match[1]);
2069
2064
  }
2070
2065
  async function writeAdminViewRegistry(projectDir, adminViewSlug) {
2071
- const adminViewsDir = path4.join(projectDir, "src", "admin-views");
2066
+ const adminViewsDir = path5.join(projectDir, "src", "admin-views");
2072
2067
  const registryPath = resolveAdminViewRegistryPath(projectDir);
2073
2068
  await fsp3.mkdir(adminViewsDir, { recursive: true });
2074
2069
  const existingAdminViewSlugs = readWorkspaceInventory(projectDir).adminViews.map((entry) => entry.slug);
2075
2070
  const existingRegistrySlugs = readAdminViewRegistrySlugs(registryPath);
2076
- const nextAdminViewSlugs = Array.from(new Set([...existingAdminViewSlugs, ...existingRegistrySlugs, adminViewSlug])).sort();
2071
+ const nextAdminViewSlugs = Array.from(new Set([
2072
+ ...existingAdminViewSlugs,
2073
+ ...existingRegistrySlugs,
2074
+ adminViewSlug
2075
+ ])).sort();
2077
2076
  await fsp3.writeFile(registryPath, buildAdminViewRegistrySource(nextAdminViewSlugs), "utf8");
2078
2077
  }
2079
- async function runAddAdminViewCommand({
2080
- adminViewName,
2081
- cwd = process.cwd(),
2082
- source
2083
- }) {
2084
- const workspace = resolveWorkspaceProject(cwd);
2085
- assertAdminViewPackageAvailability();
2086
- const adminViewSlug = assertValidGeneratedSlug("Admin view name", normalizeBlockSlug(adminViewName), "wp-typia add admin-view <name> [--source <rest-resource:slug|core-data:kind/name>]");
2087
- const parsedSource = parseAdminViewSource(source);
2088
- const inventory = readWorkspaceInventory(workspace.projectDir);
2089
- const restResource = resolveRestResourceSource(inventory.restResources, parsedSource);
2090
- const coreDataSource = isAdminViewCoreDataSource(parsedSource) ? parsedSource : undefined;
2091
- assertAdminViewDoesNotExist(workspace.projectDir, adminViewSlug, inventory);
2092
- const blockConfigPath = path4.join(workspace.projectDir, "scripts", "block-config.ts");
2078
+ async function scaffoldAdminViewWorkspace(options) {
2079
+ const {
2080
+ adminViewSlug,
2081
+ coreDataSource,
2082
+ parsedSource,
2083
+ restResource,
2084
+ workspace
2085
+ } = options;
2086
+ const blockConfigPath = path5.join(workspace.projectDir, "scripts", "block-config.ts");
2093
2087
  const bootstrapPath = getWorkspaceBootstrapPath(workspace);
2094
- const buildScriptPath = path4.join(workspace.projectDir, "scripts", "build-workspace.mjs");
2095
- const packageJsonPath = path4.join(workspace.projectDir, "package.json");
2096
- const webpackConfigPath = path4.join(workspace.projectDir, "webpack.config.js");
2088
+ const buildScriptPath = path5.join(workspace.projectDir, "scripts", "build-workspace.mjs");
2089
+ const packageJsonPath = path5.join(workspace.projectDir, "package.json");
2090
+ const webpackConfigPath = path5.join(workspace.projectDir, "webpack.config.js");
2097
2091
  const adminViewsIndexPath = resolveAdminViewRegistryPath(workspace.projectDir);
2098
- const adminViewDir = path4.join(workspace.projectDir, "src", "admin-views", adminViewSlug);
2099
- const adminViewPhpPath = path4.join(workspace.projectDir, "inc", "admin-views", `${adminViewSlug}.php`);
2092
+ const adminViewDir = path5.join(workspace.projectDir, "src", "admin-views", adminViewSlug);
2093
+ const adminViewPhpPath = path5.join(workspace.projectDir, "inc", "admin-views", `${adminViewSlug}.php`);
2100
2094
  const mutationSnapshot = {
2101
2095
  fileSources: await snapshotWorkspaceFiles([
2102
2096
  adminViewsIndexPath,
@@ -2111,37 +2105,63 @@ async function runAddAdminViewCommand({
2111
2105
  };
2112
2106
  try {
2113
2107
  await fsp3.mkdir(adminViewDir, { recursive: true });
2114
- await fsp3.mkdir(path4.dirname(adminViewPhpPath), { recursive: true });
2108
+ await fsp3.mkdir(path5.dirname(adminViewPhpPath), { recursive: true });
2115
2109
  await ensureAdminViewPackageDependencies(workspace, parsedSource);
2116
2110
  await ensureAdminViewBootstrapAnchors(workspace);
2117
2111
  await ensureAdminViewBuildScriptAnchors(workspace);
2118
2112
  await ensureAdminViewWebpackAnchors(workspace);
2119
- await fsp3.writeFile(path4.join(adminViewDir, "types.ts"), buildAdminViewTypesSource(adminViewSlug, restResource, coreDataSource), "utf8");
2120
- await fsp3.writeFile(path4.join(adminViewDir, "config.ts"), buildAdminViewConfigSource(adminViewSlug, workspace.workspace.textDomain, parsedSource, restResource), "utf8");
2121
- await fsp3.writeFile(path4.join(adminViewDir, "data.ts"), coreDataSource ? buildCoreDataAdminViewDataSource(adminViewSlug, coreDataSource) : restResource ? buildRestAdminViewDataSource(adminViewSlug, restResource) : buildDefaultAdminViewDataSource(adminViewSlug), "utf8");
2122
- await fsp3.writeFile(path4.join(adminViewDir, "Screen.tsx"), coreDataSource ? buildCoreDataAdminViewScreenSource(adminViewSlug, workspace.workspace.textDomain) : buildAdminViewScreenSource(adminViewSlug, workspace.workspace.textDomain), "utf8");
2123
- await fsp3.writeFile(path4.join(adminViewDir, "index.tsx"), buildAdminViewEntrySource(adminViewSlug), "utf8");
2124
- await fsp3.writeFile(path4.join(adminViewDir, "style.scss"), buildAdminViewStyleSource(), "utf8");
2113
+ await fsp3.writeFile(path5.join(adminViewDir, "types.ts"), buildAdminViewTypesSource(adminViewSlug, restResource, coreDataSource), "utf8");
2114
+ await fsp3.writeFile(path5.join(adminViewDir, "config.ts"), buildAdminViewConfigSource(adminViewSlug, workspace.workspace.textDomain, parsedSource, restResource), "utf8");
2115
+ await fsp3.writeFile(path5.join(adminViewDir, "data.ts"), coreDataSource ? buildCoreDataAdminViewDataSource(adminViewSlug, coreDataSource) : restResource ? buildRestAdminViewDataSource(adminViewSlug, restResource) : buildDefaultAdminViewDataSource(adminViewSlug), "utf8");
2116
+ await fsp3.writeFile(path5.join(adminViewDir, "Screen.tsx"), coreDataSource ? buildCoreDataAdminViewScreenSource(adminViewSlug, workspace.workspace.textDomain) : buildAdminViewScreenSource(adminViewSlug, workspace.workspace.textDomain), "utf8");
2117
+ await fsp3.writeFile(path5.join(adminViewDir, "index.tsx"), buildAdminViewEntrySource(adminViewSlug), "utf8");
2118
+ await fsp3.writeFile(path5.join(adminViewDir, "style.scss"), buildAdminViewStyleSource(), "utf8");
2125
2119
  await fsp3.writeFile(adminViewPhpPath, buildAdminViewPhpSource(adminViewSlug, workspace), "utf8");
2126
2120
  await writeAdminViewRegistry(workspace.projectDir, adminViewSlug);
2127
2121
  await appendWorkspaceInventoryEntries(workspace.projectDir, {
2128
- adminViewEntries: [buildAdminViewConfigEntry(adminViewSlug, parsedSource)]
2122
+ adminViewEntries: [
2123
+ buildAdminViewConfigEntry(adminViewSlug, parsedSource)
2124
+ ]
2129
2125
  });
2130
- return {
2131
- adminViewSlug,
2132
- projectDir: workspace.projectDir,
2133
- source: parsedSource ? formatAdminViewSourceLocator(parsedSource) : undefined
2134
- };
2135
2126
  } catch (error) {
2136
2127
  await rollbackWorkspaceMutation(mutationSnapshot);
2137
2128
  throw error;
2138
2129
  }
2139
2130
  }
2131
+
2132
+ // ../wp-typia-project-tools/src/runtime/cli-add-workspace-admin-view.ts
2133
+ var ADD_ADMIN_VIEW_USAGE = "wp-typia add admin-view <name> [--source <rest-resource:slug|core-data:kind/name>]";
2134
+ async function runAddAdminViewCommand({
2135
+ adminViewName,
2136
+ cwd = process.cwd(),
2137
+ source
2138
+ }) {
2139
+ const workspace = resolveWorkspaceProject(cwd);
2140
+ assertAdminViewPackageAvailability();
2141
+ const adminViewSlug = assertValidGeneratedSlug("Admin view name", normalizeBlockSlug(adminViewName), ADD_ADMIN_VIEW_USAGE);
2142
+ const parsedSource = parseAdminViewSource(source);
2143
+ const inventory = readWorkspaceInventory(workspace.projectDir);
2144
+ const restResource = resolveRestResourceSource(inventory.restResources, parsedSource);
2145
+ const coreDataSource = resolveAdminViewCoreDataSource(parsedSource);
2146
+ assertAdminViewDoesNotExist(workspace.projectDir, adminViewSlug, inventory);
2147
+ await scaffoldAdminViewWorkspace({
2148
+ adminViewSlug,
2149
+ coreDataSource,
2150
+ parsedSource,
2151
+ restResource,
2152
+ workspace
2153
+ });
2154
+ return {
2155
+ adminViewSlug,
2156
+ projectDir: workspace.projectDir,
2157
+ source: parsedSource ? formatAdminViewSourceLocator(parsedSource) : undefined
2158
+ };
2159
+ }
2140
2160
  // ../wp-typia-project-tools/src/runtime/cli-add-workspace-assets.ts
2141
2161
  var import_typescript = __toESM(require_typescript(), 1);
2142
2162
  import fs4 from "fs";
2143
2163
  import { promises as fsp4 } from "fs";
2144
- import path5 from "path";
2164
+ import path6 from "path";
2145
2165
  import {
2146
2166
  syncBlockMetadata as syncBlockMetadata2
2147
2167
  } from "@wp-typia/block-runtime/metadata-core";
@@ -2425,7 +2445,7 @@ async function ensureBindingTargetBlockAttributeType(projectDir, block, target)
2425
2445
  if (!block.attributeTypeName) {
2426
2446
  throw new Error(`Workspace block "${block.slug}" must include attributeTypeName in scripts/block-config.ts before it can receive binding-source targets.`);
2427
2447
  }
2428
- const typesPath = path5.join(projectDir, block.typesFile);
2448
+ const typesPath = path6.join(projectDir, block.typesFile);
2429
2449
  const source = await fsp4.readFile(typesPath, "utf8");
2430
2450
  const targetInterface = getInterfaceDeclaration(source, block.attributeTypeName);
2431
2451
  if (!targetInterface) {
@@ -2437,10 +2457,10 @@ async function ensureBindingTargetBlockAttributeType(projectDir, block, target)
2437
2457
  await fsp4.writeFile(typesPath, nextSource, "utf8");
2438
2458
  }
2439
2459
  await syncBlockMetadata2({
2440
- blockJsonFile: path5.join("src", "blocks", block.slug, "block.json"),
2441
- jsonSchemaFile: path5.join("src", "blocks", block.slug, "typia.schema.json"),
2442
- manifestFile: path5.join("src", "blocks", block.slug, "typia.manifest.json"),
2443
- openApiFile: path5.join("src", "blocks", block.slug, "typia.openapi.json"),
2460
+ blockJsonFile: path6.join("src", "blocks", block.slug, "block.json"),
2461
+ jsonSchemaFile: path6.join("src", "blocks", block.slug, "typia.schema.json"),
2462
+ manifestFile: path6.join("src", "blocks", block.slug, "typia.manifest.json"),
2463
+ openApiFile: path6.join("src", "blocks", block.slug, "typia.openapi.json"),
2444
2464
  projectRoot: projectDir,
2445
2465
  sourceTypeName: block.attributeTypeName,
2446
2466
  typesFile: block.typesFile
@@ -2679,7 +2699,7 @@ ${patternFunctions}
2679
2699
  }
2680
2700
  }
2681
2701
  if (!nextSource.includes(patternCategoryFunctionName) || !nextSource.includes(patternRegistrationFunctionName)) {
2682
- throw new Error(`Unable to inject pattern bootstrap functions into ${path5.basename(bootstrapPath)}.`);
2702
+ throw new Error(`Unable to inject pattern bootstrap functions into ${path6.basename(bootstrapPath)}.`);
2683
2703
  }
2684
2704
  if (!nextSource.includes(patternCategoryHook)) {
2685
2705
  nextSource = `${nextSource.trimEnd()}
@@ -2867,7 +2887,7 @@ ${snippet}
2867
2887
  if (missingReferences.length > 0) {
2868
2888
  const replacedSource = replacePhpFunctionDefinition(nextSource, enqueueFunctionName, enqueueFunction);
2869
2889
  if (!replacedSource) {
2870
- throw new Error(`Unable to repair ${path5.basename(bootstrapPath)} for ${enqueueFunctionName}.`);
2890
+ throw new Error(`Unable to repair ${path6.basename(bootstrapPath)} for ${enqueueFunctionName}.`);
2871
2891
  }
2872
2892
  nextSource = replacedSource;
2873
2893
  }
@@ -2879,7 +2899,7 @@ ${snippet}
2879
2899
  });
2880
2900
  }
2881
2901
  async function ensureEditorPluginBuildScriptAnchors(workspace) {
2882
- const buildScriptPath = path5.join(workspace.projectDir, "scripts", "build-workspace.mjs");
2902
+ const buildScriptPath = path6.join(workspace.projectDir, "scripts", "build-workspace.mjs");
2883
2903
  await patchFile(buildScriptPath, (source) => {
2884
2904
  if (/['"]src\/editor-plugins\/index\.(?:ts|js)['"]/u.test(source)) {
2885
2905
  return source;
@@ -2892,13 +2912,13 @@ async function ensureEditorPluginBuildScriptAnchors(workspace) {
2892
2912
  'src/editor-plugins/index.js',
2893
2913
  ]`);
2894
2914
  if (nextSource === source) {
2895
- throw new Error(`Unable to update ${path5.relative(workspace.projectDir, buildScriptPath)} for editor plugin shared entries.`);
2915
+ throw new Error(`Unable to update ${path6.relative(workspace.projectDir, buildScriptPath)} for editor plugin shared entries.`);
2896
2916
  }
2897
2917
  return nextSource;
2898
2918
  });
2899
2919
  }
2900
2920
  async function ensureEditorPluginWebpackAnchors(workspace) {
2901
- const webpackConfigPath = path5.join(workspace.projectDir, "webpack.config.js");
2921
+ const webpackConfigPath = path6.join(workspace.projectDir, "webpack.config.js");
2902
2922
  await patchFile(webpackConfigPath, (source) => {
2903
2923
  if (/['"]editor-plugins\/index['"]/u.test(source)) {
2904
2924
  return source;
@@ -2926,17 +2946,17 @@ async function ensureEditorPluginWebpackAnchors(workspace) {
2926
2946
  }`;
2927
2947
  const nextSource = source.replace(legacySharedEntriesBlockPattern, nextSharedEntriesBlock);
2928
2948
  if (nextSource === source) {
2929
- throw new Error(`Unable to update ${path5.relative(workspace.projectDir, webpackConfigPath)} for editor plugin shared entries.`);
2949
+ throw new Error(`Unable to update ${path6.relative(workspace.projectDir, webpackConfigPath)} for editor plugin shared entries.`);
2930
2950
  }
2931
2951
  return nextSource;
2932
2952
  });
2933
2953
  }
2934
2954
  function resolveBindingSourceRegistryPath(projectDir) {
2935
- const bindingsDir = path5.join(projectDir, "src", "bindings");
2936
- return [path5.join(bindingsDir, "index.ts"), path5.join(bindingsDir, "index.js")].find((candidatePath) => fs4.existsSync(candidatePath)) ?? path5.join(bindingsDir, "index.ts");
2955
+ const bindingsDir = path6.join(projectDir, "src", "bindings");
2956
+ return [path6.join(bindingsDir, "index.ts"), path6.join(bindingsDir, "index.js")].find((candidatePath) => fs4.existsSync(candidatePath)) ?? path6.join(bindingsDir, "index.ts");
2937
2957
  }
2938
2958
  async function writeBindingSourceRegistry(projectDir, bindingSourceSlug) {
2939
- const bindingsDir = path5.join(projectDir, "src", "bindings");
2959
+ const bindingsDir = path6.join(projectDir, "src", "bindings");
2940
2960
  const bindingsIndexPath = resolveBindingSourceRegistryPath(projectDir);
2941
2961
  await fsp4.mkdir(bindingsDir, { recursive: true });
2942
2962
  const existingBindingSourceSlugs = fs4.readdirSync(bindingsDir, { withFileTypes: true }).filter((entry) => entry.isDirectory()).map((entry) => entry.name);
@@ -2944,11 +2964,11 @@ async function writeBindingSourceRegistry(projectDir, bindingSourceSlug) {
2944
2964
  await fsp4.writeFile(bindingsIndexPath, buildBindingSourceIndexSource(nextBindingSourceSlugs), "utf8");
2945
2965
  }
2946
2966
  function resolveEditorPluginRegistryPath(projectDir) {
2947
- const editorPluginsDir = path5.join(projectDir, "src", "editor-plugins");
2967
+ const editorPluginsDir = path6.join(projectDir, "src", "editor-plugins");
2948
2968
  return [
2949
- path5.join(editorPluginsDir, "index.ts"),
2950
- path5.join(editorPluginsDir, "index.js")
2951
- ].find((candidatePath) => fs4.existsSync(candidatePath)) ?? path5.join(editorPluginsDir, "index.ts");
2969
+ path6.join(editorPluginsDir, "index.ts"),
2970
+ path6.join(editorPluginsDir, "index.js")
2971
+ ].find((candidatePath) => fs4.existsSync(candidatePath)) ?? path6.join(editorPluginsDir, "index.ts");
2952
2972
  }
2953
2973
  function readEditorPluginRegistrySlugs(registryPath) {
2954
2974
  if (!fs4.existsSync(registryPath)) {
@@ -2958,7 +2978,7 @@ function readEditorPluginRegistrySlugs(registryPath) {
2958
2978
  return Array.from(source.matchAll(/^\s*import\s+['"]\.\/([^/'"]+)(?:\/index(?:\.[cm]?[jt]sx?)?)?['"];?\s*$/gmu)).map((match) => match[1]);
2959
2979
  }
2960
2980
  async function writeEditorPluginRegistry(projectDir, editorPluginSlug) {
2961
- const editorPluginsDir = path5.join(projectDir, "src", "editor-plugins");
2981
+ const editorPluginsDir = path6.join(projectDir, "src", "editor-plugins");
2962
2982
  const registryPath = resolveEditorPluginRegistryPath(projectDir);
2963
2983
  await fsp4.mkdir(editorPluginsDir, { recursive: true });
2964
2984
  const existingEditorPluginSlugs = readWorkspaceInventory(projectDir).editorPlugins.map((entry) => entry.slug);
@@ -2976,17 +2996,17 @@ async function runAddEditorPluginCommand({
2976
2996
  const resolvedSlot = assertValidEditorPluginSlot(slot);
2977
2997
  const inventory = readWorkspaceInventory(workspace.projectDir);
2978
2998
  assertEditorPluginDoesNotExist(workspace.projectDir, editorPluginSlug, inventory);
2979
- const blockConfigPath = path5.join(workspace.projectDir, "scripts", "block-config.ts");
2999
+ const blockConfigPath = path6.join(workspace.projectDir, "scripts", "block-config.ts");
2980
3000
  const bootstrapPath = getWorkspaceBootstrapPath(workspace);
2981
- const buildScriptPath = path5.join(workspace.projectDir, "scripts", "build-workspace.mjs");
3001
+ const buildScriptPath = path6.join(workspace.projectDir, "scripts", "build-workspace.mjs");
2982
3002
  const editorPluginsIndexPath = resolveEditorPluginRegistryPath(workspace.projectDir);
2983
- const webpackConfigPath = path5.join(workspace.projectDir, "webpack.config.js");
2984
- const editorPluginDir = path5.join(workspace.projectDir, "src", "editor-plugins", editorPluginSlug);
2985
- const entryFilePath = path5.join(editorPluginDir, "index.tsx");
2986
- const surfaceFilePath = path5.join(editorPluginDir, "Surface.tsx");
2987
- const dataFilePath = path5.join(editorPluginDir, "data.ts");
2988
- const typesFilePath = path5.join(editorPluginDir, "types.ts");
2989
- const styleFilePath = path5.join(editorPluginDir, "style.scss");
3003
+ const webpackConfigPath = path6.join(workspace.projectDir, "webpack.config.js");
3004
+ const editorPluginDir = path6.join(workspace.projectDir, "src", "editor-plugins", editorPluginSlug);
3005
+ const entryFilePath = path6.join(editorPluginDir, "index.tsx");
3006
+ const surfaceFilePath = path6.join(editorPluginDir, "Surface.tsx");
3007
+ const dataFilePath = path6.join(editorPluginDir, "data.ts");
3008
+ const typesFilePath = path6.join(editorPluginDir, "types.ts");
3009
+ const styleFilePath = path6.join(editorPluginDir, "style.scss");
2990
3010
  const mutationSnapshot = {
2991
3011
  fileSources: await snapshotWorkspaceFiles([
2992
3012
  blockConfigPath,
@@ -3032,16 +3052,16 @@ async function runAddPatternCommand({
3032
3052
  const patternSlug = assertValidGeneratedSlug("Pattern name", normalizeBlockSlug(patternName), "wp-typia add pattern <name>");
3033
3053
  const inventory = readWorkspaceInventory(workspace.projectDir);
3034
3054
  assertPatternDoesNotExist(workspace.projectDir, patternSlug, inventory);
3035
- const blockConfigPath = path5.join(workspace.projectDir, "scripts", "block-config.ts");
3055
+ const blockConfigPath = path6.join(workspace.projectDir, "scripts", "block-config.ts");
3036
3056
  const bootstrapPath = getWorkspaceBootstrapPath(workspace);
3037
- const patternFilePath = path5.join(workspace.projectDir, "src", "patterns", `${patternSlug}.php`);
3057
+ const patternFilePath = path6.join(workspace.projectDir, "src", "patterns", `${patternSlug}.php`);
3038
3058
  const mutationSnapshot = {
3039
3059
  fileSources: await snapshotWorkspaceFiles([blockConfigPath, bootstrapPath]),
3040
3060
  snapshotDirs: [],
3041
3061
  targetPaths: [patternFilePath]
3042
3062
  };
3043
3063
  try {
3044
- await fsp4.mkdir(path5.dirname(patternFilePath), { recursive: true });
3064
+ await fsp4.mkdir(path6.dirname(patternFilePath), { recursive: true });
3045
3065
  await ensurePatternBootstrapAnchors(workspace);
3046
3066
  await fsp4.writeFile(patternFilePath, buildPatternSource(patternSlug, workspace.workspace.namespace, workspace.workspace.textDomain), "utf8");
3047
3067
  await appendWorkspaceInventoryEntries(workspace.projectDir, {
@@ -3071,18 +3091,18 @@ async function runAddBindingSourceCommand({
3071
3091
  blockName
3072
3092
  }, workspace.workspace.namespace);
3073
3093
  const targetBlock = target ? resolveWorkspaceBlock(inventory, target.blockSlug) : undefined;
3074
- const blockConfigPath = path5.join(workspace.projectDir, "scripts", "block-config.ts");
3094
+ const blockConfigPath = path6.join(workspace.projectDir, "scripts", "block-config.ts");
3075
3095
  const bootstrapPath = getWorkspaceBootstrapPath(workspace);
3076
3096
  const bindingsIndexPath = resolveBindingSourceRegistryPath(workspace.projectDir);
3077
- const bindingSourceDir = path5.join(workspace.projectDir, "src", "bindings", bindingSourceSlug);
3078
- const serverFilePath = path5.join(bindingSourceDir, "server.php");
3079
- const editorFilePath = path5.join(bindingSourceDir, "editor.ts");
3080
- const blockJsonPath = target ? path5.join(workspace.projectDir, "src", "blocks", target.blockSlug, "block.json") : undefined;
3097
+ const bindingSourceDir = path6.join(workspace.projectDir, "src", "bindings", bindingSourceSlug);
3098
+ const serverFilePath = path6.join(bindingSourceDir, "server.php");
3099
+ const editorFilePath = path6.join(bindingSourceDir, "editor.ts");
3100
+ const blockJsonPath = target ? path6.join(workspace.projectDir, "src", "blocks", target.blockSlug, "block.json") : undefined;
3081
3101
  const targetGeneratedMetadataPaths = target ? [
3082
- path5.join(workspace.projectDir, "src", "blocks", target.blockSlug, "typia.manifest.json"),
3083
- path5.join(workspace.projectDir, "src", "blocks", target.blockSlug, "typia.openapi.json"),
3084
- path5.join(workspace.projectDir, "src", "blocks", target.blockSlug, "typia.schema.json"),
3085
- path5.join(workspace.projectDir, "src", "blocks", target.blockSlug, "typia-validator.php")
3102
+ path6.join(workspace.projectDir, "src", "blocks", target.blockSlug, "typia.manifest.json"),
3103
+ path6.join(workspace.projectDir, "src", "blocks", target.blockSlug, "typia.openapi.json"),
3104
+ path6.join(workspace.projectDir, "src", "blocks", target.blockSlug, "typia.schema.json"),
3105
+ path6.join(workspace.projectDir, "src", "blocks", target.blockSlug, "typia-validator.php")
3086
3106
  ] : [];
3087
3107
  const mutationSnapshot = {
3088
3108
  fileSources: await snapshotWorkspaceFiles([
@@ -3090,7 +3110,7 @@ async function runAddBindingSourceCommand({
3090
3110
  bootstrapPath,
3091
3111
  bindingsIndexPath,
3092
3112
  ...blockJsonPath ? [blockJsonPath] : [],
3093
- ...targetBlock ? [path5.join(workspace.projectDir, targetBlock.typesFile)] : [],
3113
+ ...targetBlock ? [path6.join(workspace.projectDir, targetBlock.typesFile)] : [],
3094
3114
  ...targetGeneratedMetadataPaths
3095
3115
  ]),
3096
3116
  snapshotDirs: [],
@@ -3120,10 +3140,10 @@ async function runAddBindingSourceCommand({
3120
3140
  }
3121
3141
  // ../wp-typia-project-tools/src/runtime/cli-add-workspace-rest.ts
3122
3142
  import { promises as fsp5 } from "fs";
3123
- import path8 from "path";
3143
+ import path9 from "path";
3124
3144
 
3125
3145
  // ../wp-typia-project-tools/src/runtime/cli-add-workspace-rest-anchors.ts
3126
- import path6 from "path";
3146
+ import path7 from "path";
3127
3147
  var REST_RESOURCE_SERVER_GLOB = "/inc/rest/*.php";
3128
3148
  async function ensureRestResourceBootstrapAnchors(workspace) {
3129
3149
  const bootstrapPath = getWorkspaceBootstrapPath(workspace);
@@ -3171,7 +3191,7 @@ ${snippet}
3171
3191
  insertPhpSnippet(registerFunction);
3172
3192
  } else if (!nextSource.includes(REST_RESOURCE_SERVER_GLOB)) {
3173
3193
  throw new Error([
3174
- `Unable to patch ${path6.basename(bootstrapPath)} in ensureRestResourceBootstrapAnchors.`,
3194
+ `Unable to patch ${path7.basename(bootstrapPath)} in ensureRestResourceBootstrapAnchors.`,
3175
3195
  `The existing ${registerFunctionName}() definition does not include ${REST_RESOURCE_SERVER_GLOB}.`,
3176
3196
  "Restore the generated bootstrap shape or wire the REST resource loader manually before retrying."
3177
3197
  ].join(" "));
@@ -3185,7 +3205,7 @@ ${snippet}
3185
3205
  function assertSyncRestAnchor(nextSource, target, anchorDescription, hasAnchor, syncRestScriptPath) {
3186
3206
  if (!nextSource.includes(target) && !hasAnchor) {
3187
3207
  throw new Error([
3188
- `ensureRestResourceSyncScriptAnchors could not patch ${path6.basename(syncRestScriptPath)}.`,
3208
+ `ensureRestResourceSyncScriptAnchors could not patch ${path7.basename(syncRestScriptPath)}.`,
3189
3209
  `Missing expected ${anchorDescription} anchor in scripts/sync-rest-contracts.ts.`,
3190
3210
  "Restore the generated template or add the REST_RESOURCES wiring manually before retrying."
3191
3211
  ].join(" "));
@@ -3200,7 +3220,7 @@ function replaceRequiredSyncRestSource(nextSource, target, anchor, replacement,
3200
3220
  return nextSource.replace(anchor, replacement);
3201
3221
  }
3202
3222
  async function ensureRestResourceSyncScriptAnchors(workspace) {
3203
- const syncRestScriptPath = path6.join(workspace.projectDir, "scripts", "sync-rest-contracts.ts");
3223
+ const syncRestScriptPath = path7.join(workspace.projectDir, "scripts", "sync-rest-contracts.ts");
3204
3224
  await patchFile(syncRestScriptPath, (source) => {
3205
3225
  let nextSource = source;
3206
3226
  const importAnchor = "import { BLOCKS, type WorkspaceBlockConfig } from './block-config';";
@@ -3318,7 +3338,7 @@ async function ensureRestResourceSyncScriptAnchors(workspace) {
3318
3338
  }
3319
3339
 
3320
3340
  // ../wp-typia-project-tools/src/runtime/rest-resource-artifacts.ts
3321
- import path7 from "path";
3341
+ import path8 from "path";
3322
3342
  import {
3323
3343
  defineEndpointManifest,
3324
3344
  syncEndpointClient,
@@ -3454,8 +3474,8 @@ async function syncRestResourceArtifacts({
3454
3474
  const manifest = buildRestResourceEndpointManifest(variables, methods);
3455
3475
  for (const [baseName, contract] of Object.entries(manifest.contracts)) {
3456
3476
  await syncTypeSchemas({
3457
- jsonSchemaFile: path7.join(outputDir, "api-schemas", `${baseName}.schema.json`),
3458
- openApiFile: path7.join(outputDir, "api-schemas", `${baseName}.openapi.json`),
3477
+ jsonSchemaFile: path8.join(outputDir, "api-schemas", `${baseName}.schema.json`),
3478
+ openApiFile: path8.join(outputDir, "api-schemas", `${baseName}.openapi.json`),
3459
3479
  projectRoot: projectDir,
3460
3480
  sourceTypeName: contract.sourceTypeName,
3461
3481
  typesFile
@@ -3463,7 +3483,7 @@ async function syncRestResourceArtifacts({
3463
3483
  }
3464
3484
  await syncRestOpenApi({
3465
3485
  manifest,
3466
- openApiFile: path7.join(outputDir, "api.openapi.json"),
3486
+ openApiFile: path8.join(outputDir, "api.openapi.json"),
3467
3487
  projectRoot: projectDir,
3468
3488
  typesFile
3469
3489
  });
@@ -4288,15 +4308,15 @@ async function runAddRestResourceCommand({
4288
4308
  const resolvedNamespace = resolveRestResourceNamespace(workspace.workspace.namespace, namespace);
4289
4309
  const inventory = readWorkspaceInventory(workspace.projectDir);
4290
4310
  assertRestResourceDoesNotExist(workspace.projectDir, restResourceSlug, inventory);
4291
- const blockConfigPath = path8.join(workspace.projectDir, "scripts", "block-config.ts");
4311
+ const blockConfigPath = path9.join(workspace.projectDir, "scripts", "block-config.ts");
4292
4312
  const bootstrapPath = getWorkspaceBootstrapPath(workspace);
4293
- const syncRestScriptPath = path8.join(workspace.projectDir, "scripts", "sync-rest-contracts.ts");
4294
- const restResourceDir = path8.join(workspace.projectDir, "src", "rest", restResourceSlug);
4295
- const typesFilePath = path8.join(restResourceDir, "api-types.ts");
4296
- const validatorsFilePath = path8.join(restResourceDir, "api-validators.ts");
4297
- const apiFilePath = path8.join(restResourceDir, "api.ts");
4298
- const dataFilePath = path8.join(restResourceDir, "data.ts");
4299
- const phpFilePath = path8.join(workspace.projectDir, "inc", "rest", `${restResourceSlug}.php`);
4313
+ const syncRestScriptPath = path9.join(workspace.projectDir, "scripts", "sync-rest-contracts.ts");
4314
+ const restResourceDir = path9.join(workspace.projectDir, "src", "rest", restResourceSlug);
4315
+ const typesFilePath = path9.join(restResourceDir, "api-types.ts");
4316
+ const validatorsFilePath = path9.join(restResourceDir, "api-validators.ts");
4317
+ const apiFilePath = path9.join(restResourceDir, "api.ts");
4318
+ const dataFilePath = path9.join(restResourceDir, "data.ts");
4319
+ const phpFilePath = path9.join(workspace.projectDir, "inc", "rest", `${restResourceSlug}.php`);
4300
4320
  const mutationSnapshot = {
4301
4321
  fileSources: await snapshotWorkspaceFiles([
4302
4322
  blockConfigPath,
@@ -4308,7 +4328,7 @@ async function runAddRestResourceCommand({
4308
4328
  };
4309
4329
  try {
4310
4330
  await fsp5.mkdir(restResourceDir, { recursive: true });
4311
- await fsp5.mkdir(path8.dirname(phpFilePath), { recursive: true });
4331
+ await fsp5.mkdir(path9.dirname(phpFilePath), { recursive: true });
4312
4332
  await ensureRestResourceBootstrapAnchors(workspace);
4313
4333
  await ensureRestResourceSyncScriptAnchors(workspace);
4314
4334
  await fsp5.writeFile(typesFilePath, buildRestResourceTypesSource(restResourceSlug, resolvedMethods), "utf8");
@@ -4351,7 +4371,7 @@ async function runAddRestResourceCommand({
4351
4371
  var import_semver = __toESM(require_semver(), 1);
4352
4372
  import fs5 from "fs";
4353
4373
  import { promises as fsp6 } from "fs";
4354
- import path9 from "path";
4374
+ import path10 from "path";
4355
4375
  import { syncTypeSchemas as syncTypeSchemas2 } from "@wp-typia/block-runtime/metadata-core";
4356
4376
  var ABILITY_SERVER_GLOB = "/inc/abilities/*.php";
4357
4377
  var ABILITY_EDITOR_SCRIPT = "build/abilities/index.js";
@@ -4836,8 +4856,8 @@ function buildAbilityRegistrySource(abilitySlugs) {
4836
4856
  `);
4837
4857
  }
4838
4858
  function resolveAbilityRegistryPath(projectDir) {
4839
- const abilitiesDir = path9.join(projectDir, "src", "abilities");
4840
- return [path9.join(abilitiesDir, "index.ts"), path9.join(abilitiesDir, "index.js")].find((candidatePath) => fs5.existsSync(candidatePath)) ?? path9.join(abilitiesDir, "index.ts");
4859
+ const abilitiesDir = path10.join(projectDir, "src", "abilities");
4860
+ return [path10.join(abilitiesDir, "index.ts"), path10.join(abilitiesDir, "index.js")].find((candidatePath) => fs5.existsSync(candidatePath)) ?? path10.join(abilitiesDir, "index.ts");
4841
4861
  }
4842
4862
  function readAbilityRegistrySlugs(registryPath) {
4843
4863
  if (!fs5.existsSync(registryPath)) {
@@ -4847,7 +4867,7 @@ function readAbilityRegistrySlugs(registryPath) {
4847
4867
  return Array.from(source.matchAll(/^\s*export\s+\*\s+from\s+['"]\.\/([^/'"]+)\/client['"];?\s*$/gmu)).map((match) => match[1]);
4848
4868
  }
4849
4869
  async function writeAbilityRegistry(projectDir, abilitySlug) {
4850
- const abilitiesDir = path9.join(projectDir, "src", "abilities");
4870
+ const abilitiesDir = path10.join(projectDir, "src", "abilities");
4851
4871
  const registryPath = resolveAbilityRegistryPath(projectDir);
4852
4872
  await fsp6.mkdir(abilitiesDir, { recursive: true });
4853
4873
  const existingAbilitySlugs = readWorkspaceInventory(projectDir).abilities.map((entry) => entry.slug);
@@ -4979,7 +4999,7 @@ ${snippet}
4979
4999
  });
4980
5000
  }
4981
5001
  async function ensureAbilityPackageScripts(workspace) {
4982
- const packageJsonPath = path9.join(workspace.projectDir, "package.json");
5002
+ const packageJsonPath = path10.join(workspace.projectDir, "package.json");
4983
5003
  const packageJson = JSON.parse(await fsp6.readFile(packageJsonPath, "utf8"));
4984
5004
  const nextScripts = {
4985
5005
  ...packageJson.scripts ?? {},
@@ -4999,7 +5019,7 @@ async function ensureAbilityPackageScripts(workspace) {
4999
5019
  `, "utf8");
5000
5020
  }
5001
5021
  async function ensureAbilitySyncProjectAnchors(workspace) {
5002
- const syncProjectScriptPath = path9.join(workspace.projectDir, "scripts", "sync-project.ts");
5022
+ const syncProjectScriptPath = path10.join(workspace.projectDir, "scripts", "sync-project.ts");
5003
5023
  await patchFile(syncProjectScriptPath, (source) => {
5004
5024
  let nextSource = source;
5005
5025
  const syncRestConst = "const syncRestScriptPath = path.join( 'scripts', 'sync-rest-contracts.ts' );";
@@ -5014,7 +5034,7 @@ async function ensureAbilitySyncProjectAnchors(workspace) {
5014
5034
  if (!nextSource.includes(syncAbilitiesConst)) {
5015
5035
  if (!nextSource.includes(syncRestConst)) {
5016
5036
  throw new Error([
5017
- `ensureAbilitySyncProjectAnchors could not patch ${path9.basename(syncProjectScriptPath)}.`,
5037
+ `ensureAbilitySyncProjectAnchors could not patch ${path10.basename(syncProjectScriptPath)}.`,
5018
5038
  "Missing the expected sync-rest script constant in scripts/sync-project.ts.",
5019
5039
  "Restore the generated template or wire sync-abilities manually before retrying."
5020
5040
  ].join(" "));
@@ -5025,7 +5045,7 @@ ${syncAbilitiesConst}`);
5025
5045
  if (!nextSource.includes("runSyncScript( syncAbilitiesScriptPath, options );")) {
5026
5046
  if (!syncRestBlockPattern.test(nextSource)) {
5027
5047
  throw new Error([
5028
- `ensureAbilitySyncProjectAnchors could not patch ${path9.basename(syncProjectScriptPath)}.`,
5048
+ `ensureAbilitySyncProjectAnchors could not patch ${path10.basename(syncProjectScriptPath)}.`,
5029
5049
  "Missing the expected sync-rest invocation block in scripts/sync-project.ts.",
5030
5050
  "Restore the generated template or wire sync-abilities manually before retrying."
5031
5051
  ].join(" "));
@@ -5038,7 +5058,7 @@ ${syncAbilitiesBlock}`);
5038
5058
  });
5039
5059
  }
5040
5060
  async function ensureAbilityBuildScriptAnchors(workspace) {
5041
- const buildScriptPath = path9.join(workspace.projectDir, "scripts", "build-workspace.mjs");
5061
+ const buildScriptPath = path10.join(workspace.projectDir, "scripts", "build-workspace.mjs");
5042
5062
  await patchFile(buildScriptPath, (source) => {
5043
5063
  let nextSource = source;
5044
5064
  if (/['"]src\/abilities\/index\.(?:ts|js)['"]/u.test(nextSource)) {
@@ -5048,7 +5068,7 @@ async function ensureAbilityBuildScriptAnchors(workspace) {
5048
5068
  const match = nextSource.match(sharedEntriesPattern);
5049
5069
  if (!match || !match[2].includes("src/bindings/index.ts") || !match[2].includes("src/editor-plugins/index.ts")) {
5050
5070
  throw new Error([
5051
- `ensureAbilityBuildScriptAnchors could not patch ${path9.basename(buildScriptPath)}.`,
5071
+ `ensureAbilityBuildScriptAnchors could not patch ${path10.basename(buildScriptPath)}.`,
5052
5072
  "Missing the expected shared editor entries array in scripts/build-workspace.mjs.",
5053
5073
  "Restore the generated template or wire abilities/index manually before retrying."
5054
5074
  ].join(" "));
@@ -5065,7 +5085,7 @@ async function ensureAbilityBuildScriptAnchors(workspace) {
5065
5085
  });
5066
5086
  }
5067
5087
  async function ensureAbilityWebpackAnchors(workspace) {
5068
- const webpackConfigPath = path9.join(workspace.projectDir, "webpack.config.js");
5088
+ const webpackConfigPath = path10.join(workspace.projectDir, "webpack.config.js");
5069
5089
  await patchFile(webpackConfigPath, (source) => {
5070
5090
  if (/['"]abilities\/index['"]/u.test(source)) {
5071
5091
  return source;
@@ -5096,7 +5116,7 @@ $2`);
5096
5116
  const match = source.match(sharedEntriesPattern);
5097
5117
  if (!match || !match[1].includes("bindings/index") || !match[1].includes("editor-plugins/index")) {
5098
5118
  throw new Error([
5099
- `ensureAbilityWebpackAnchors could not patch ${path9.basename(webpackConfigPath)}.`,
5119
+ `ensureAbilityWebpackAnchors could not patch ${path10.basename(webpackConfigPath)}.`,
5100
5120
  "Missing the expected shared editor entries block in webpack.config.js.",
5101
5121
  "Restore the generated template or wire abilities/index manually before retrying."
5102
5122
  ].join(" "));
@@ -5126,20 +5146,20 @@ async function runAddAbilityCommand({
5126
5146
  const inventory = readWorkspaceInventory(workspace.projectDir);
5127
5147
  assertAbilityDoesNotExist(workspace.projectDir, abilitySlug, inventory);
5128
5148
  const compatibilityPolicy = resolveScaffoldCompatibilityPolicy(REQUIRED_WORKSPACE_ABILITY_COMPATIBILITY);
5129
- const blockConfigPath = path9.join(workspace.projectDir, "scripts", "block-config.ts");
5149
+ const blockConfigPath = path10.join(workspace.projectDir, "scripts", "block-config.ts");
5130
5150
  const bootstrapPath = getWorkspaceBootstrapPath(workspace);
5131
- const buildScriptPath = path9.join(workspace.projectDir, "scripts", "build-workspace.mjs");
5132
- const packageJsonPath = path9.join(workspace.projectDir, "package.json");
5133
- const syncAbilitiesScriptPath = path9.join(workspace.projectDir, "scripts", "sync-abilities.ts");
5134
- const syncProjectScriptPath = path9.join(workspace.projectDir, "scripts", "sync-project.ts");
5135
- const webpackConfigPath = path9.join(workspace.projectDir, "webpack.config.js");
5151
+ const buildScriptPath = path10.join(workspace.projectDir, "scripts", "build-workspace.mjs");
5152
+ const packageJsonPath = path10.join(workspace.projectDir, "package.json");
5153
+ const syncAbilitiesScriptPath = path10.join(workspace.projectDir, "scripts", "sync-abilities.ts");
5154
+ const syncProjectScriptPath = path10.join(workspace.projectDir, "scripts", "sync-project.ts");
5155
+ const webpackConfigPath = path10.join(workspace.projectDir, "webpack.config.js");
5136
5156
  const abilitiesIndexPath = resolveAbilityRegistryPath(workspace.projectDir);
5137
- const abilityDir = path9.join(workspace.projectDir, "src", "abilities", abilitySlug);
5138
- const configFilePath = path9.join(abilityDir, "ability.config.json");
5139
- const typesFilePath = path9.join(abilityDir, "types.ts");
5140
- const dataFilePath = path9.join(abilityDir, "data.ts");
5141
- const clientFilePath = path9.join(abilityDir, "client.ts");
5142
- const phpFilePath = path9.join(workspace.projectDir, "inc", "abilities", `${abilitySlug}.php`);
5157
+ const abilityDir = path10.join(workspace.projectDir, "src", "abilities", abilitySlug);
5158
+ const configFilePath = path10.join(abilityDir, "ability.config.json");
5159
+ const typesFilePath = path10.join(abilityDir, "types.ts");
5160
+ const dataFilePath = path10.join(abilityDir, "data.ts");
5161
+ const clientFilePath = path10.join(abilityDir, "client.ts");
5162
+ const phpFilePath = path10.join(workspace.projectDir, "inc", "abilities", `${abilitySlug}.php`);
5143
5163
  const mutationSnapshot = {
5144
5164
  fileSources: await snapshotWorkspaceFiles([
5145
5165
  blockConfigPath,
@@ -5156,7 +5176,7 @@ async function runAddAbilityCommand({
5156
5176
  };
5157
5177
  try {
5158
5178
  await fsp6.mkdir(abilityDir, { recursive: true });
5159
- await fsp6.mkdir(path9.dirname(phpFilePath), { recursive: true });
5179
+ await fsp6.mkdir(path10.dirname(phpFilePath), { recursive: true });
5160
5180
  await ensureAbilityBootstrapAnchors(workspace);
5161
5181
  await patchFile(bootstrapPath, (source) => updatePluginHeaderCompatibility(source, compatibilityPolicy));
5162
5182
  await ensureAbilityPackageScripts(workspace);
@@ -5197,11 +5217,11 @@ async function runAddAbilityCommand({
5197
5217
  }
5198
5218
  // ../wp-typia-project-tools/src/runtime/cli-add-workspace-ai.ts
5199
5219
  import { promises as fsp8 } from "fs";
5200
- import path12 from "path";
5220
+ import path13 from "path";
5201
5221
 
5202
5222
  // ../wp-typia-project-tools/src/runtime/ai-feature-artifacts.ts
5203
5223
  import { mkdir, readFile, writeFile } from "fs/promises";
5204
- import path10 from "path";
5224
+ import path11 from "path";
5205
5225
  import {
5206
5226
  defineEndpointManifest as defineEndpointManifest2,
5207
5227
  syncEndpointClient as syncEndpointClient2,
@@ -5227,7 +5247,7 @@ function normalizeGeneratedArtifactContent(content) {
5227
5247
  }
5228
5248
  async function reconcileGeneratedArtifact(options) {
5229
5249
  if (!options.check) {
5230
- await mkdir(path10.dirname(options.filePath), {
5250
+ await mkdir(path11.dirname(options.filePath), {
5231
5251
  recursive: true
5232
5252
  });
5233
5253
  await writeFile(options.filePath, options.content, "utf8");
@@ -5299,8 +5319,8 @@ async function syncAiFeatureRestArtifacts({
5299
5319
  const manifest = buildAiFeatureEndpointManifest(variables);
5300
5320
  for (const [baseName, contract] of Object.entries(manifest.contracts)) {
5301
5321
  await syncTypeSchemas3({
5302
- jsonSchemaFile: path10.join(outputDir, "api-schemas", `${baseName}.schema.json`),
5303
- openApiFile: path10.join(outputDir, "api-schemas", `${baseName}.openapi.json`),
5322
+ jsonSchemaFile: path11.join(outputDir, "api-schemas", `${baseName}.schema.json`),
5323
+ openApiFile: path11.join(outputDir, "api-schemas", `${baseName}.openapi.json`),
5304
5324
  projectRoot: projectDir,
5305
5325
  sourceTypeName: contract.sourceTypeName,
5306
5326
  typesFile
@@ -5308,7 +5328,7 @@ async function syncAiFeatureRestArtifacts({
5308
5328
  }
5309
5329
  await syncRestOpenApi2({
5310
5330
  manifest,
5311
- openApiFile: path10.join(outputDir, "api.openapi.json"),
5331
+ openApiFile: path11.join(outputDir, "api.openapi.json"),
5312
5332
  projectRoot: projectDir,
5313
5333
  typesFile
5314
5334
  }, executionOptions);
@@ -5326,19 +5346,19 @@ async function syncAiFeatureSchemaArtifact({
5326
5346
  outputDir,
5327
5347
  projectDir
5328
5348
  }) {
5329
- const sourceSchemaPath = path10.join(projectDir, outputDir, "api-schemas", "feature-result.schema.json");
5349
+ const sourceSchemaPath = path11.join(projectDir, outputDir, "api-schemas", "feature-result.schema.json");
5330
5350
  const responseSchema = assertJsonObject(JSON.parse(await readFile(sourceSchemaPath, "utf8")), sourceSchemaPath);
5331
5351
  const aiSchema = projectWordPressAiSchema(responseSchema);
5332
5352
  await reconcileGeneratedArtifact({
5333
5353
  check,
5334
5354
  content: `${JSON.stringify(aiSchema, null, 2)}
5335
5355
  `,
5336
- filePath: path10.join(projectDir, aiSchemaFile),
5356
+ filePath: path11.join(projectDir, aiSchemaFile),
5337
5357
  label: "AI feature schema"
5338
5358
  });
5339
5359
  return {
5340
5360
  aiSchema,
5341
- aiSchemaPath: path10.join(projectDir, aiSchemaFile),
5361
+ aiSchemaPath: path11.join(projectDir, aiSchemaFile),
5342
5362
  check,
5343
5363
  sourceSchemaPath
5344
5364
  };
@@ -5416,6 +5436,45 @@ export interface ${pascalCase}AiFeatureResponse {
5416
5436
  result: ${pascalCase}AiFeatureResult;
5417
5437
  telemetry: ${pascalCase}AiFeatureTelemetry;
5418
5438
  }
5439
+
5440
+ export type ${pascalCase}AiFeatureSupportProbeMode = 'request-time';
5441
+
5442
+ export type ${pascalCase}AiFeatureUnavailableErrorCode =
5443
+ 'ai_client_unavailable';
5444
+
5445
+ export type ${pascalCase}AiFeatureUnavailableReasonCode =
5446
+ | 'missing-wordpress-ai-client'
5447
+ | 'request-time-support-probe';
5448
+
5449
+ export interface ${pascalCase}AiFeatureSupportReason {
5450
+ code: ${pascalCase}AiFeatureUnavailableReasonCode;
5451
+ label: string & tags.MinLength< 1 > & tags.MaxLength< 160 >;
5452
+ message: string & tags.MinLength< 1 > & tags.MaxLength< 4000 >;
5453
+ }
5454
+
5455
+ export interface ${pascalCase}AiFeatureSupportMetadata {
5456
+ featureLabel: string & tags.MinLength< 1 > & tags.MaxLength< 160 >;
5457
+ featureSlug: string & tags.MinLength< 1 > & tags.MaxLength< 160 >;
5458
+ compatibility: {
5459
+ hardMinimums: {
5460
+ php?: string;
5461
+ wordpress?: string;
5462
+ };
5463
+ mode: 'baseline' | 'optional' | 'required';
5464
+ optionalFeatureIds: string[];
5465
+ optionalFeatures: string[];
5466
+ requiredFeatureIds: string[];
5467
+ requiredFeatures: string[];
5468
+ runtimeGates: string[];
5469
+ };
5470
+ supportProbe: {
5471
+ endpointMethod: 'POST';
5472
+ endpointPath: string & tags.MinLength< 1 > & tags.MaxLength< 200 >;
5473
+ mode: ${pascalCase}AiFeatureSupportProbeMode;
5474
+ unavailableErrorCode: ${pascalCase}AiFeatureUnavailableErrorCode;
5475
+ };
5476
+ unavailableReasons: ${pascalCase}AiFeatureSupportReason[];
5477
+ }
5419
5478
  `;
5420
5479
  }
5421
5480
  function buildAiFeatureValidatorsSource(aiFeatureSlug) {
@@ -5451,6 +5510,8 @@ export const apiValidators = {
5451
5510
  }
5452
5511
  function buildAiFeatureApiSource(aiFeatureSlug) {
5453
5512
  const pascalCase = toPascalCase(aiFeatureSlug);
5513
+ const compatibility = createScaffoldCompatibilityConfig(resolveScaffoldCompatibilityPolicy(OPTIONAL_WORDPRESS_AI_CLIENT_COMPATIBILITY));
5514
+ const title = toTitleCase(aiFeatureSlug);
5454
5515
  return `import {
5455
5516
  callEndpoint,
5456
5517
  resolveRestRouteUrl,
@@ -5458,6 +5519,7 @@ function buildAiFeatureApiSource(aiFeatureSlug) {
5458
5519
 
5459
5520
  import type {
5460
5521
  ${pascalCase}AiFeatureRequest,
5522
+ ${pascalCase}AiFeatureSupportMetadata,
5461
5523
  } from './api-types';
5462
5524
  import {
5463
5525
  run${pascalCase}AiFeatureEndpoint,
@@ -5484,6 +5546,14 @@ function resolveRestNonce( fallback?: string ): string | undefined {
5484
5546
  : undefined;
5485
5547
  }
5486
5548
 
5549
+ function isPlainObject( value: unknown ): value is Record< string, unknown > {
5550
+ return (
5551
+ !! value &&
5552
+ typeof value === 'object' &&
5553
+ ! Array.isArray( value )
5554
+ );
5555
+ }
5556
+
5487
5557
  export const aiFeatureRunEndpoint = {
5488
5558
  ...run${pascalCase}AiFeatureEndpoint,
5489
5559
  buildRequestOptions: () => {
@@ -5499,6 +5569,63 @@ export const aiFeatureRunEndpoint = {
5499
5569
  },
5500
5570
  };
5501
5571
 
5572
+ export const aiFeatureSupportMetadata = {
5573
+ compatibility: ${JSON.stringify(compatibility, null, "\t")},
5574
+ featureLabel: ${quoteTsString(title)},
5575
+ featureSlug: ${quoteTsString(aiFeatureSlug)},
5576
+ supportProbe: {
5577
+ endpointMethod: 'POST',
5578
+ endpointPath: aiFeatureRunEndpoint.path,
5579
+ mode: 'request-time',
5580
+ unavailableErrorCode: 'ai_client_unavailable',
5581
+ },
5582
+ unavailableReasons: [
5583
+ {
5584
+ code: 'missing-wordpress-ai-client',
5585
+ label: 'WordPress AI Client unavailable',
5586
+ message:
5587
+ 'This AI feature stays disabled until the WordPress AI Client is available on the site.',
5588
+ },
5589
+ {
5590
+ code: 'request-time-support-probe',
5591
+ label: 'Support is checked at request time',
5592
+ message:
5593
+ 'Support is verified when the feature runs, so editor and admin UIs should degrade gracefully when the site rejects the request.',
5594
+ },
5595
+ ],
5596
+ } satisfies ${pascalCase}AiFeatureSupportMetadata;
5597
+
5598
+ export function getAiFeatureSupportHintLines() {
5599
+ return aiFeatureSupportMetadata.unavailableReasons.map(
5600
+ ( reason ) => reason.message
5601
+ );
5602
+ }
5603
+
5604
+ export function isAiFeatureSupportUnavailableError( error: unknown ) {
5605
+ if ( ! isPlainObject( error ) ) {
5606
+ return false;
5607
+ }
5608
+
5609
+ const data = isPlainObject( error.data ) ? error.data : undefined;
5610
+ return (
5611
+ error.code === aiFeatureSupportMetadata.supportProbe.unavailableErrorCode ||
5612
+ data?.status === 501
5613
+ );
5614
+ }
5615
+
5616
+ export function resolveAiFeatureUnavailableMessage( error: unknown ) {
5617
+ if (
5618
+ isPlainObject( error ) &&
5619
+ typeof error.message === 'string' &&
5620
+ error.message.length > 0
5621
+ ) {
5622
+ return error.message;
5623
+ }
5624
+
5625
+ return aiFeatureSupportMetadata.unavailableReasons[ 0 ]?.message ??
5626
+ 'This AI feature is currently unavailable.';
5627
+ }
5628
+
5502
5629
  export function runAiFeature( request: ${pascalCase}AiFeatureRequest ) {
5503
5630
  return callEndpoint( aiFeatureRunEndpoint, request );
5504
5631
  }
@@ -5517,6 +5644,10 @@ import type {
5517
5644
  } from './api-types';
5518
5645
  import {
5519
5646
  aiFeatureRunEndpoint,
5647
+ aiFeatureSupportMetadata,
5648
+ getAiFeatureSupportHintLines,
5649
+ isAiFeatureSupportUnavailableError,
5650
+ resolveAiFeatureUnavailableMessage,
5520
5651
  } from './api';
5521
5652
 
5522
5653
  export type UseRun${pascalCase}AiFeatureMutationOptions =
@@ -5531,6 +5662,13 @@ export function useRun${pascalCase}AiFeatureMutation(
5531
5662
  ) {
5532
5663
  return useEndpointMutation( aiFeatureRunEndpoint, options );
5533
5664
  }
5665
+
5666
+ export {
5667
+ aiFeatureSupportMetadata,
5668
+ getAiFeatureSupportHintLines,
5669
+ isAiFeatureSupportUnavailableError,
5670
+ resolveAiFeatureUnavailableMessage,
5671
+ };
5534
5672
  `;
5535
5673
  }
5536
5674
  function buildAiFeatureSyncScriptSource() {
@@ -5662,7 +5800,7 @@ main().catch( ( error ) => {
5662
5800
 
5663
5801
  // ../wp-typia-project-tools/src/runtime/cli-add-workspace-ai-anchors.ts
5664
5802
  import { promises as fsp7 } from "fs";
5665
- import path11 from "path";
5803
+ import path12 from "path";
5666
5804
  var AI_FEATURE_SERVER_GLOB = "/inc/ai-features/*.php";
5667
5805
  async function ensureAiFeatureBootstrapAnchors(workspace) {
5668
5806
  const bootstrapPath = getWorkspaceBootstrapPath(workspace);
@@ -5710,7 +5848,7 @@ ${snippet}
5710
5848
  insertPhpSnippet(registerFunction);
5711
5849
  } else if (!nextSource.includes(AI_FEATURE_SERVER_GLOB)) {
5712
5850
  throw new Error([
5713
- `Unable to patch ${path11.basename(bootstrapPath)} in ensureAiFeatureBootstrapAnchors.`,
5851
+ `Unable to patch ${path12.basename(bootstrapPath)} in ensureAiFeatureBootstrapAnchors.`,
5714
5852
  `The existing ${registerFunctionName}() definition does not include ${AI_FEATURE_SERVER_GLOB}.`,
5715
5853
  "Restore the generated bootstrap shape or wire the AI feature loader manually before retrying."
5716
5854
  ].join(" "));
@@ -5722,7 +5860,7 @@ ${snippet}
5722
5860
  });
5723
5861
  }
5724
5862
  async function ensureAiFeaturePackageScripts(workspace) {
5725
- const packageJsonPath = path11.join(workspace.projectDir, "package.json");
5863
+ const packageJsonPath = path12.join(workspace.projectDir, "package.json");
5726
5864
  const packageJson = JSON.parse(await fsp7.readFile(packageJsonPath, "utf8"));
5727
5865
  const nextScripts = {
5728
5866
  ...packageJson.scripts ?? {},
@@ -5750,7 +5888,7 @@ async function ensureAiFeaturePackageScripts(workspace) {
5750
5888
  };
5751
5889
  }
5752
5890
  async function ensureAiFeatureSyncProjectAnchors(workspace) {
5753
- const syncProjectScriptPath = path11.join(workspace.projectDir, "scripts", "sync-project.ts");
5891
+ const syncProjectScriptPath = path12.join(workspace.projectDir, "scripts", "sync-project.ts");
5754
5892
  await patchFile(syncProjectScriptPath, (source) => {
5755
5893
  let nextSource = source;
5756
5894
  const syncRestConst = "const syncRestScriptPath = path.join( 'scripts', 'sync-rest-contracts.ts' );";
@@ -5765,7 +5903,7 @@ async function ensureAiFeatureSyncProjectAnchors(workspace) {
5765
5903
  if (!nextSource.includes(syncAiConst)) {
5766
5904
  if (!nextSource.includes(syncRestConst)) {
5767
5905
  throw new Error([
5768
- `ensureAiFeatureSyncProjectAnchors could not patch ${path11.basename(syncProjectScriptPath)}.`,
5906
+ `ensureAiFeatureSyncProjectAnchors could not patch ${path12.basename(syncProjectScriptPath)}.`,
5769
5907
  "Missing the expected sync-rest script constant in scripts/sync-project.ts.",
5770
5908
  "Restore the generated template or wire sync-ai manually before retrying."
5771
5909
  ].join(" "));
@@ -5776,7 +5914,7 @@ ${syncAiConst}`);
5776
5914
  if (!nextSource.includes("runSyncScript( syncAiScriptPath, options );")) {
5777
5915
  if (!syncRestBlockPattern.test(nextSource)) {
5778
5916
  throw new Error([
5779
- `ensureAiFeatureSyncProjectAnchors could not patch ${path11.basename(syncProjectScriptPath)}.`,
5917
+ `ensureAiFeatureSyncProjectAnchors could not patch ${path12.basename(syncProjectScriptPath)}.`,
5780
5918
  "Missing the expected sync-rest invocation block in scripts/sync-project.ts.",
5781
5919
  "Restore the generated template or wire sync-ai manually before retrying."
5782
5920
  ].join(" "));
@@ -5791,7 +5929,7 @@ ${syncAiBlock}`);
5791
5929
  function assertSyncRestAnchor2(nextSource, target, anchorDescription, hasAnchor, syncRestScriptPath) {
5792
5930
  if (!nextSource.includes(target) && !hasAnchor) {
5793
5931
  throw new Error([
5794
- `ensureAiFeatureSyncRestAnchors could not patch ${path11.basename(syncRestScriptPath)}.`,
5932
+ `ensureAiFeatureSyncRestAnchors could not patch ${path12.basename(syncRestScriptPath)}.`,
5795
5933
  `Missing expected ${anchorDescription} anchor in scripts/sync-rest-contracts.ts.`,
5796
5934
  "Restore the generated template or add the AI feature wiring manually before retrying."
5797
5935
  ].join(" "));
@@ -5806,7 +5944,7 @@ function replaceRequiredSyncRestSource2(nextSource, target, anchor, replacement,
5806
5944
  return nextSource.replace(anchor, replacement);
5807
5945
  }
5808
5946
  async function ensureAiFeatureSyncRestAnchors(workspace) {
5809
- const syncRestScriptPath = path11.join(workspace.projectDir, "scripts", "sync-rest-contracts.ts");
5947
+ const syncRestScriptPath = path12.join(workspace.projectDir, "scripts", "sync-rest-contracts.ts");
5810
5948
  await patchFile(syncRestScriptPath, (source) => {
5811
5949
  let nextSource = source;
5812
5950
  const importAnchor = [
@@ -5943,18 +6081,40 @@ function buildAiFeaturePhpSource(aiFeatureSlug, namespace, phpPrefix, textDomain
5943
6081
  const normalizeSchemaFunctionName = `${phpPrefix}_${aiFeaturePhpId}_sanitize_ai_feature_schema`;
5944
6082
  const validatePayloadFunctionName = `${phpPrefix}_${aiFeaturePhpId}_validate_ai_feature_payload`;
5945
6083
  const canManageFunctionName = `${phpPrefix}_${aiFeaturePhpId}_can_manage_ai_feature`;
6084
+ const normalizePromptPayloadFunctionName = `${phpPrefix}_${aiFeaturePhpId}_normalize_ai_feature_prompt_payload`;
5946
6085
  const buildPromptFunctionName = `${phpPrefix}_${aiFeaturePhpId}_build_ai_feature_prompt`;
6086
+ const resolvePromptOptionsFunctionName = `${phpPrefix}_${aiFeaturePhpId}_resolve_ai_feature_prompt_options`;
5947
6087
  const normalizeProviderTypeFunctionName = `${phpPrefix}_${aiFeaturePhpId}_normalize_provider_type`;
5948
6088
  const buildTelemetryFunctionName = `${phpPrefix}_${aiFeaturePhpId}_build_ai_feature_telemetry`;
6089
+ const resolveUnavailableMessageFunctionName = `${phpPrefix}_${aiFeaturePhpId}_resolve_ai_feature_unavailable_message`;
5949
6090
  const isSupportedFunctionName = `${phpPrefix}_${aiFeaturePhpId}_is_ai_feature_supported`;
5950
6091
  const adminNoticeFunctionName = `${phpPrefix}_${aiFeaturePhpId}_ai_feature_admin_notice`;
5951
6092
  const handlerFunctionName = `${phpPrefix}_${aiFeaturePhpId}_handle_run_ai_feature`;
5952
6093
  const registerRoutesFunctionName = `${phpPrefix}_${aiFeaturePhpId}_register_ai_feature_routes`;
6094
+ const permissionFilterHook = `${phpPrefix}_${aiFeaturePhpId}_ai_feature_permission`;
6095
+ const promptPayloadFilterHook = `${phpPrefix}_${aiFeaturePhpId}_ai_feature_prompt_payload`;
6096
+ const promptFilterHook = `${phpPrefix}_${aiFeaturePhpId}_ai_feature_prompt`;
6097
+ const promptOptionsFilterHook = `${phpPrefix}_${aiFeaturePhpId}_ai_feature_prompt_options`;
6098
+ const adminNoticeMessageFilterHook = `${phpPrefix}_${aiFeaturePhpId}_ai_feature_admin_notice_message`;
6099
+ const unavailableMessageFilterHook = `${phpPrefix}_${aiFeaturePhpId}_ai_feature_unavailable_message`;
6100
+ const telemetryFilterHook = `${phpPrefix}_${aiFeaturePhpId}_ai_feature_telemetry`;
5953
6101
  return `<?php
5954
6102
  if ( ! defined( 'ABSPATH' ) ) {
5955
6103
  return;
5956
6104
  }
5957
6105
 
6106
+ /*
6107
+ * Customization hooks for the ${aiFeatureTitle} AI feature:
6108
+ *
6109
+ * - ${quotePhpString(permissionFilterHook)} filters the default current_user_can( 'edit_posts' ) capability check.
6110
+ * - ${quotePhpString(promptPayloadFilterHook)} filters the validated request payload array before prompt serialization.
6111
+ * - ${quotePhpString(promptFilterHook)} filters the final prompt string after payload normalization.
6112
+ * - ${quotePhpString(promptOptionsFilterHook)} filters prompt options with \`temperature\` and \`modelPreference\` keys.
6113
+ * - ${quotePhpString(adminNoticeMessageFilterHook)} filters the wp-admin notice shown when AI support is unavailable.
6114
+ * - ${quotePhpString(unavailableMessageFilterHook)} filters REST-facing unavailable messages by reason code.
6115
+ * - ${quotePhpString(telemetryFilterHook)} filters the response telemetry array before schema validation. Return a schema-compatible array.
6116
+ */
6117
+
5958
6118
  if ( ! function_exists( '${loadSchemaFunctionName}' ) ) {
5959
6119
  function ${loadSchemaFunctionName}( $schema_name ) {
5960
6120
  $project_root = dirname( __DIR__, 2 );
@@ -6021,17 +6181,111 @@ if ( ! function_exists( '${validatePayloadFunctionName}' ) ) {
6021
6181
  }
6022
6182
 
6023
6183
  if ( ! function_exists( '${canManageFunctionName}' ) ) {
6024
- function ${canManageFunctionName}() {
6025
- return current_user_can( 'edit_posts' );
6184
+ function ${canManageFunctionName}( WP_REST_Request $request = null ) {
6185
+ $permission = apply_filters(
6186
+ ${quotePhpString(permissionFilterHook)},
6187
+ current_user_can( 'edit_posts' ),
6188
+ $request
6189
+ );
6190
+ if ( is_wp_error( $permission ) ) {
6191
+ return $permission;
6192
+ }
6193
+ return (bool) $permission;
6194
+ }
6195
+ }
6196
+
6197
+ if ( ! function_exists( '${normalizePromptPayloadFunctionName}' ) ) {
6198
+ function ${normalizePromptPayloadFunctionName}( array $payload ) {
6199
+ $normalized_payload = apply_filters(
6200
+ ${quotePhpString(promptPayloadFilterHook)},
6201
+ $payload
6202
+ );
6203
+ return is_array( $normalized_payload ) ? $normalized_payload : $payload;
6026
6204
  }
6027
6205
  }
6028
6206
 
6029
6207
  if ( ! function_exists( '${buildPromptFunctionName}' ) ) {
6030
6208
  function ${buildPromptFunctionName}( array $payload ) {
6031
- return sprintf(
6209
+ $normalized_payload = ${normalizePromptPayloadFunctionName}( $payload );
6210
+ $prompt = sprintf(
6032
6211
  'You are helping with the %1$s AI workflow. Read the JSON request payload and return JSON that matches the provided schema. Request payload: %2$s',
6033
6212
  ${quotePhpString(aiFeatureTitle)},
6034
- wp_json_encode( $payload )
6213
+ wp_json_encode( $normalized_payload )
6214
+ );
6215
+ $filtered_prompt = apply_filters(
6216
+ ${quotePhpString(promptFilterHook)},
6217
+ $prompt,
6218
+ $normalized_payload,
6219
+ $payload
6220
+ );
6221
+ return is_string( $filtered_prompt ) && '' !== $filtered_prompt ? $filtered_prompt : $prompt;
6222
+ }
6223
+ }
6224
+
6225
+ if ( ! function_exists( '${resolvePromptOptionsFunctionName}' ) ) {
6226
+ function ${resolvePromptOptionsFunctionName}( array $payload = array() ) {
6227
+ $options = apply_filters(
6228
+ ${quotePhpString(promptOptionsFilterHook)},
6229
+ array(
6230
+ 'modelPreference' => array(),
6231
+ 'temperature' => 0.2,
6232
+ ),
6233
+ $payload
6234
+ );
6235
+ if ( ! is_array( $options ) ) {
6236
+ $options = array();
6237
+ }
6238
+
6239
+ $temperature = 0.2;
6240
+ if ( array_key_exists( 'temperature', $options ) ) {
6241
+ if ( null === $options['temperature'] ) {
6242
+ $temperature = null;
6243
+ } elseif ( is_numeric( $options['temperature'] ) ) {
6244
+ $temperature = (float) $options['temperature'];
6245
+ }
6246
+ }
6247
+
6248
+ $model_preferences = array();
6249
+ if ( isset( $options['modelPreference'] ) ) {
6250
+ $raw_model_preferences = $options['modelPreference'];
6251
+ if ( is_string( $raw_model_preferences ) && '' !== $raw_model_preferences ) {
6252
+ $model_preferences = array( $raw_model_preferences );
6253
+ } elseif ( is_array( $raw_model_preferences ) ) {
6254
+ $model_preferences = array_values(
6255
+ array_filter(
6256
+ array_map(
6257
+ static function ( $candidate ) {
6258
+ if ( is_string( $candidate ) && '' !== $candidate ) {
6259
+ return $candidate;
6260
+ }
6261
+ if ( ! is_array( $candidate ) ) {
6262
+ return null;
6263
+ }
6264
+
6265
+ $normalized = array_values(
6266
+ array_filter(
6267
+ $candidate,
6268
+ static function ( $value ) {
6269
+ return is_string( $value ) && '' !== $value;
6270
+ }
6271
+ )
6272
+ );
6273
+
6274
+ return count( $normalized ) > 0 ? $normalized : null;
6275
+ },
6276
+ $raw_model_preferences
6277
+ ),
6278
+ static function ( $candidate ) {
6279
+ return null !== $candidate;
6280
+ }
6281
+ )
6282
+ );
6283
+ }
6284
+ }
6285
+
6286
+ return array(
6287
+ 'modelPreference' => $model_preferences,
6288
+ 'temperature' => $temperature,
6035
6289
  );
6036
6290
  }
6037
6291
  }
@@ -6047,7 +6301,7 @@ if ( ! function_exists( '${normalizeProviderTypeFunctionName}' ) ) {
6047
6301
  }
6048
6302
 
6049
6303
  if ( ! function_exists( '${buildTelemetryFunctionName}' ) ) {
6050
- function ${buildTelemetryFunctionName}( $result ) {
6304
+ function ${buildTelemetryFunctionName}( $result, array $payload = array(), array $normalized_result = array() ) {
6051
6305
  if (
6052
6306
  ! is_object( $result ) ||
6053
6307
  ! method_exists( $result, 'getId' ) ||
@@ -6107,47 +6361,95 @@ if ( ! function_exists( '${buildTelemetryFunctionName}' ) ) {
6107
6361
  }
6108
6362
  }
6109
6363
 
6110
- return $telemetry;
6364
+ $filtered_telemetry = apply_filters(
6365
+ ${quotePhpString(telemetryFilterHook)},
6366
+ $telemetry,
6367
+ $result,
6368
+ $payload,
6369
+ $normalized_result
6370
+ );
6371
+ return is_array( $filtered_telemetry ) ? $filtered_telemetry : $telemetry;
6372
+ }
6373
+ }
6374
+
6375
+ if ( ! function_exists( '${resolveUnavailableMessageFunctionName}' ) ) {
6376
+ function ${resolveUnavailableMessageFunctionName}( $message, $reason, array $context = array() ) {
6377
+ $filtered_message = apply_filters(
6378
+ ${quotePhpString(unavailableMessageFilterHook)},
6379
+ $message,
6380
+ $reason,
6381
+ $context
6382
+ );
6383
+ return is_string( $filtered_message ) && '' !== $filtered_message ? $filtered_message : $message;
6111
6384
  }
6112
6385
  }
6113
6386
 
6114
6387
  if ( ! function_exists( '${isSupportedFunctionName}' ) ) {
6115
- function ${isSupportedFunctionName}() {
6388
+ function ${isSupportedFunctionName}( array $payload = array(), $cache_result = true ) {
6116
6389
  static $is_supported = null;
6117
- if ( null !== $is_supported ) {
6390
+ $use_cache = $cache_result && count( $payload ) === 0;
6391
+ if ( $use_cache && null !== $is_supported ) {
6118
6392
  return $is_supported;
6119
6393
  }
6120
6394
 
6121
6395
  if ( ! function_exists( 'wp_ai_client_prompt' ) ) {
6122
- $is_supported = false;
6123
- return $is_supported;
6396
+ if ( $use_cache ) {
6397
+ $is_supported = false;
6398
+ }
6399
+ return false;
6124
6400
  }
6125
6401
 
6126
6402
  $schema = ${loadAiSchemaFunctionName}();
6127
6403
  if ( ! is_array( $schema ) ) {
6128
- $is_supported = false;
6129
- return $is_supported;
6404
+ if ( $use_cache ) {
6405
+ $is_supported = false;
6406
+ }
6407
+ return false;
6130
6408
  }
6131
6409
 
6132
6410
  $prompt = wp_ai_client_prompt( 'AI feature support probe.' );
6133
6411
  if ( ! is_object( $prompt ) || ! method_exists( $prompt, 'as_json_response' ) ) {
6134
- $is_supported = false;
6135
- return $is_supported;
6412
+ if ( $use_cache ) {
6413
+ $is_supported = false;
6414
+ }
6415
+ return false;
6416
+ }
6417
+ $prompt_options = ${resolvePromptOptionsFunctionName}( $payload );
6418
+ if (
6419
+ array_key_exists( 'temperature', $prompt_options ) &&
6420
+ null !== $prompt_options['temperature'] &&
6421
+ method_exists( $prompt, 'using_temperature' )
6422
+ ) {
6423
+ $prompt = $prompt->using_temperature( $prompt_options['temperature'] );
6424
+ }
6425
+ if (
6426
+ ! empty( $prompt_options['modelPreference'] ) &&
6427
+ method_exists( $prompt, 'using_model_preference' )
6428
+ ) {
6429
+ $prompt = $prompt->using_model_preference( ...$prompt_options['modelPreference'] );
6136
6430
  }
6137
6431
 
6138
6432
  $structured_prompt = $prompt->as_json_response( $schema );
6139
6433
  if ( ! is_object( $structured_prompt ) ) {
6140
- $is_supported = false;
6141
- return $is_supported;
6434
+ if ( $use_cache ) {
6435
+ $is_supported = false;
6436
+ }
6437
+ return false;
6142
6438
  }
6143
6439
 
6144
6440
  if ( method_exists( $structured_prompt, 'is_supported_for_text_generation' ) ) {
6145
- $is_supported = (bool) $structured_prompt->is_supported_for_text_generation();
6146
- return $is_supported;
6441
+ $supported = (bool) $structured_prompt->is_supported_for_text_generation();
6442
+ if ( $use_cache ) {
6443
+ $is_supported = $supported;
6444
+ }
6445
+ return $supported;
6147
6446
  }
6148
6447
 
6149
- $is_supported = method_exists( $structured_prompt, 'generate_text_result' );
6150
- return $is_supported;
6448
+ $supported = method_exists( $structured_prompt, 'generate_text_result' );
6449
+ if ( $use_cache ) {
6450
+ $is_supported = $supported;
6451
+ }
6452
+ return $supported;
6151
6453
  }
6152
6454
  }
6153
6455
 
@@ -6162,6 +6464,18 @@ if ( ! function_exists( '${adminNoticeFunctionName}' ) ) {
6162
6464
  __( 'The %s AI feature is optional and remains disabled until the WordPress AI Client is available with structured text generation support for the generated schema.', ${quotePhpString(textDomain)} ),
6163
6465
  ${quotePhpString(aiFeatureTitle)}
6164
6466
  );
6467
+ $filtered_message = apply_filters(
6468
+ ${quotePhpString(adminNoticeMessageFilterHook)},
6469
+ $message,
6470
+ array(
6471
+ 'featureSlug' => ${quotePhpString(aiFeatureSlug)},
6472
+ 'featureTitle' => ${quotePhpString(aiFeatureTitle)},
6473
+ 'namespace' => ${quotePhpString(namespace)},
6474
+ )
6475
+ );
6476
+ if ( is_string( $filtered_message ) && '' !== $filtered_message ) {
6477
+ $message = $filtered_message;
6478
+ }
6165
6479
  printf( '<div class="notice notice-warning"><p>%s</p></div>', esc_html( $message ) );
6166
6480
  }
6167
6481
  }
@@ -6173,10 +6487,16 @@ if ( ! function_exists( '${handlerFunctionName}' ) ) {
6173
6487
  return $payload;
6174
6488
  }
6175
6489
 
6176
- if ( ! ${isSupportedFunctionName}() ) {
6490
+ if ( ! ${isSupportedFunctionName}( $payload, false ) ) {
6177
6491
  return new WP_Error(
6178
6492
  'ai_client_unavailable',
6179
- 'The WordPress AI Client is unavailable or does not support this feature endpoint.',
6493
+ ${resolveUnavailableMessageFunctionName}(
6494
+ 'The WordPress AI Client is unavailable or does not support this feature endpoint.',
6495
+ 'support_probe_failed',
6496
+ array(
6497
+ 'featureSlug' => ${quotePhpString(aiFeatureSlug)},
6498
+ )
6499
+ ),
6180
6500
  array( 'status' => 501 )
6181
6501
  );
6182
6502
  }
@@ -6190,22 +6510,45 @@ if ( ! function_exists( '${handlerFunctionName}' ) ) {
6190
6510
  );
6191
6511
  }
6192
6512
 
6513
+ $prompt_options = ${resolvePromptOptionsFunctionName}( $payload );
6193
6514
  $prompt = wp_ai_client_prompt( ${buildPromptFunctionName}( $payload ) );
6194
6515
  if ( ! is_object( $prompt ) ) {
6195
6516
  return new WP_Error(
6196
6517
  'ai_client_unavailable',
6197
- 'The WordPress AI Client prompt builder is unavailable on this site.',
6518
+ ${resolveUnavailableMessageFunctionName}(
6519
+ 'The WordPress AI Client prompt builder is unavailable on this site.',
6520
+ 'prompt_builder_missing',
6521
+ array(
6522
+ 'featureSlug' => ${quotePhpString(aiFeatureSlug)},
6523
+ )
6524
+ ),
6198
6525
  array( 'status' => 501 )
6199
6526
  );
6200
6527
  }
6201
6528
 
6202
- if ( method_exists( $prompt, 'using_temperature' ) ) {
6203
- $prompt = $prompt->using_temperature( 0.2 );
6529
+ if (
6530
+ array_key_exists( 'temperature', $prompt_options ) &&
6531
+ null !== $prompt_options['temperature'] &&
6532
+ method_exists( $prompt, 'using_temperature' )
6533
+ ) {
6534
+ $prompt = $prompt->using_temperature( $prompt_options['temperature'] );
6535
+ }
6536
+ if (
6537
+ ! empty( $prompt_options['modelPreference'] ) &&
6538
+ method_exists( $prompt, 'using_model_preference' )
6539
+ ) {
6540
+ $prompt = $prompt->using_model_preference( ...$prompt_options['modelPreference'] );
6204
6541
  }
6205
6542
  if ( ! method_exists( $prompt, 'as_json_response' ) ) {
6206
6543
  return new WP_Error(
6207
6544
  'ai_client_unavailable',
6208
- 'The current WordPress AI Client does not expose as_json_response().',
6545
+ ${resolveUnavailableMessageFunctionName}(
6546
+ 'The current WordPress AI Client does not expose as_json_response().',
6547
+ 'as_json_response_missing',
6548
+ array(
6549
+ 'featureSlug' => ${quotePhpString(aiFeatureSlug)},
6550
+ )
6551
+ ),
6209
6552
  array( 'status' => 501 )
6210
6553
  );
6211
6554
  }
@@ -6214,7 +6557,13 @@ if ( ! function_exists( '${handlerFunctionName}' ) ) {
6214
6557
  if ( ! is_object( $structured_prompt ) ) {
6215
6558
  return new WP_Error(
6216
6559
  'ai_client_unavailable',
6217
- 'The current WordPress AI Client could not prepare a structured-output prompt.',
6560
+ ${resolveUnavailableMessageFunctionName}(
6561
+ 'The current WordPress AI Client could not prepare a structured-output prompt.',
6562
+ 'structured_prompt_missing',
6563
+ array(
6564
+ 'featureSlug' => ${quotePhpString(aiFeatureSlug)},
6565
+ )
6566
+ ),
6218
6567
  array( 'status' => 501 )
6219
6568
  );
6220
6569
  }
@@ -6225,14 +6574,26 @@ if ( ! function_exists( '${handlerFunctionName}' ) ) {
6225
6574
  ) {
6226
6575
  return new WP_Error(
6227
6576
  'ai_client_unavailable',
6228
- 'The current WordPress AI Client provider or model does not support this structured-output feature.',
6577
+ ${resolveUnavailableMessageFunctionName}(
6578
+ 'The current WordPress AI Client provider or model does not support this structured-output feature.',
6579
+ 'text_generation_unsupported',
6580
+ array(
6581
+ 'featureSlug' => ${quotePhpString(aiFeatureSlug)},
6582
+ )
6583
+ ),
6229
6584
  array( 'status' => 501 )
6230
6585
  );
6231
6586
  }
6232
6587
  if ( ! method_exists( $structured_prompt, 'generate_text_result' ) ) {
6233
6588
  return new WP_Error(
6234
6589
  'ai_client_unavailable',
6235
- 'The current WordPress AI Client does not expose generate_text_result() after as_json_response().',
6590
+ ${resolveUnavailableMessageFunctionName}(
6591
+ 'The current WordPress AI Client does not expose generate_text_result() after as_json_response().',
6592
+ 'generate_text_result_missing',
6593
+ array(
6594
+ 'featureSlug' => ${quotePhpString(aiFeatureSlug)},
6595
+ )
6596
+ ),
6236
6597
  array( 'status' => 501 )
6237
6598
  );
6238
6599
  }
@@ -6267,7 +6628,7 @@ if ( ! function_exists( '${handlerFunctionName}' ) ) {
6267
6628
  );
6268
6629
  }
6269
6630
 
6270
- $telemetry = ${buildTelemetryFunctionName}( $result );
6631
+ $telemetry = ${buildTelemetryFunctionName}( $result, $payload, $normalized_result );
6271
6632
  if ( is_wp_error( $telemetry ) ) {
6272
6633
  return $telemetry;
6273
6634
  }
@@ -6323,18 +6684,18 @@ async function runAddAiFeatureCommand({
6323
6684
  const compatibilityPolicy = resolveScaffoldCompatibilityPolicy(OPTIONAL_WORDPRESS_AI_CLIENT_COMPATIBILITY);
6324
6685
  const inventory = readWorkspaceInventory(workspace.projectDir);
6325
6686
  assertAiFeatureDoesNotExist(workspace.projectDir, aiFeatureSlug, inventory);
6326
- const blockConfigPath = path12.join(workspace.projectDir, "scripts", "block-config.ts");
6687
+ const blockConfigPath = path13.join(workspace.projectDir, "scripts", "block-config.ts");
6327
6688
  const bootstrapPath = getWorkspaceBootstrapPath(workspace);
6328
- const packageJsonPath = path12.join(workspace.projectDir, "package.json");
6329
- const syncAiScriptPath = path12.join(workspace.projectDir, "scripts", "sync-ai-features.ts");
6330
- const syncProjectScriptPath = path12.join(workspace.projectDir, "scripts", "sync-project.ts");
6331
- const syncRestScriptPath = path12.join(workspace.projectDir, "scripts", "sync-rest-contracts.ts");
6332
- const aiFeatureDir = path12.join(workspace.projectDir, "src", "ai-features", aiFeatureSlug);
6333
- const typesFilePath = path12.join(aiFeatureDir, "api-types.ts");
6334
- const validatorsFilePath = path12.join(aiFeatureDir, "api-validators.ts");
6335
- const apiFilePath = path12.join(aiFeatureDir, "api.ts");
6336
- const dataFilePath = path12.join(aiFeatureDir, "data.ts");
6337
- const phpFilePath = path12.join(workspace.projectDir, "inc", "ai-features", `${aiFeatureSlug}.php`);
6689
+ const packageJsonPath = path13.join(workspace.projectDir, "package.json");
6690
+ const syncAiScriptPath = path13.join(workspace.projectDir, "scripts", "sync-ai-features.ts");
6691
+ const syncProjectScriptPath = path13.join(workspace.projectDir, "scripts", "sync-project.ts");
6692
+ const syncRestScriptPath = path13.join(workspace.projectDir, "scripts", "sync-rest-contracts.ts");
6693
+ const aiFeatureDir = path13.join(workspace.projectDir, "src", "ai-features", aiFeatureSlug);
6694
+ const typesFilePath = path13.join(aiFeatureDir, "api-types.ts");
6695
+ const validatorsFilePath = path13.join(aiFeatureDir, "api-validators.ts");
6696
+ const apiFilePath = path13.join(aiFeatureDir, "api.ts");
6697
+ const dataFilePath = path13.join(aiFeatureDir, "data.ts");
6698
+ const phpFilePath = path13.join(workspace.projectDir, "inc", "ai-features", `${aiFeatureSlug}.php`);
6338
6699
  const mutationSnapshot = {
6339
6700
  fileSources: await snapshotWorkspaceFiles([
6340
6701
  blockConfigPath,
@@ -6349,7 +6710,7 @@ async function runAddAiFeatureCommand({
6349
6710
  };
6350
6711
  try {
6351
6712
  await fsp8.mkdir(aiFeatureDir, { recursive: true });
6352
- await fsp8.mkdir(path12.dirname(phpFilePath), { recursive: true });
6713
+ await fsp8.mkdir(path13.dirname(phpFilePath), { recursive: true });
6353
6714
  await ensureAiFeatureBootstrapAnchors(workspace);
6354
6715
  await patchFile(bootstrapPath, (source) => updatePluginHeaderCompatibility(source, compatibilityPolicy));
6355
6716
  const packageScriptChanges = await ensureAiFeaturePackageScripts(workspace);
@@ -6364,7 +6725,7 @@ async function runAddAiFeatureCommand({
6364
6725
  const pascalCase = toPascalCase(aiFeatureSlug);
6365
6726
  await syncAiFeatureRestArtifacts({
6366
6727
  clientFile: `src/ai-features/${aiFeatureSlug}/api-client.ts`,
6367
- outputDir: path12.join("src", "ai-features", aiFeatureSlug),
6728
+ outputDir: path13.join("src", "ai-features", aiFeatureSlug),
6368
6729
  projectDir: workspace.projectDir,
6369
6730
  typesFile: `src/ai-features/${aiFeatureSlug}/api-types.ts`,
6370
6731
  validatorsFile: `src/ai-features/${aiFeatureSlug}/api-validators.ts`,
@@ -6377,7 +6738,7 @@ async function runAddAiFeatureCommand({
6377
6738
  });
6378
6739
  await syncAiFeatureSchemaArtifact({
6379
6740
  aiSchemaFile: `src/ai-features/${aiFeatureSlug}/ai-schemas/feature-result.ai.schema.json`,
6380
- outputDir: path12.join("src", "ai-features", aiFeatureSlug),
6741
+ outputDir: path13.join("src", "ai-features", aiFeatureSlug),
6381
6742
  projectDir: workspace.projectDir
6382
6743
  });
6383
6744
  await appendWorkspaceInventoryEntries(workspace.projectDir, {
@@ -6798,7 +7159,7 @@ ${VARIATIONS_CALL_LINE}
6798
7159
  }
6799
7160
  }
6800
7161
  if (!hasExecutablePattern(nextSource, VARIATIONS_CALL_PATTERN)) {
6801
- throw new Error(`Unable to inject ${VARIATIONS_CALL_LINE} into ${path13.basename(blockIndexPath)}.`);
7162
+ throw new Error(`Unable to inject ${VARIATIONS_CALL_LINE} into ${path14.basename(blockIndexPath)}.`);
6802
7163
  }
6803
7164
  return nextSource;
6804
7165
  });
@@ -6828,7 +7189,7 @@ ${BLOCK_STYLES_CALL_LINE}
6828
7189
  }
6829
7190
  }
6830
7191
  if (!hasExecutablePattern(nextSource, BLOCK_STYLES_CALL_PATTERN)) {
6831
- throw new Error(`Unable to inject ${BLOCK_STYLES_CALL_LINE} into ${path13.basename(blockIndexPath)}.`);
7192
+ throw new Error(`Unable to inject ${BLOCK_STYLES_CALL_LINE} into ${path14.basename(blockIndexPath)}.`);
6832
7193
  }
6833
7194
  return nextSource;
6834
7195
  });
@@ -6845,7 +7206,7 @@ ${nextSource}`;
6845
7206
  SCAFFOLD_REGISTRATION_SETTINGS_CALL_PATTERN
6846
7207
  ]);
6847
7208
  if (!callRange) {
6848
- throw new Error(`Unable to inject ${BLOCK_TRANSFORMS_CALL_LINE} into ${path13.basename(blockIndexPath)} because it does not expose a scaffold registration settings object.`);
7209
+ throw new Error(`Unable to inject ${BLOCK_TRANSFORMS_CALL_LINE} into ${path14.basename(blockIndexPath)} because it does not expose a scaffold registration settings object.`);
6849
7210
  }
6850
7211
  nextSource = [
6851
7212
  nextSource.slice(0, callRange.start),
@@ -6858,42 +7219,42 @@ ${nextSource}`;
6858
7219
  });
6859
7220
  }
6860
7221
  async function writeVariationRegistry(projectDir, blockSlug, variationSlug) {
6861
- const variationsDir = path13.join(projectDir, "src", "blocks", blockSlug, "variations");
6862
- const variationsIndexPath = path13.join(variationsDir, "index.ts");
7222
+ const variationsDir = path14.join(projectDir, "src", "blocks", blockSlug, "variations");
7223
+ const variationsIndexPath = path14.join(variationsDir, "index.ts");
6863
7224
  await fsp9.mkdir(variationsDir, { recursive: true });
6864
7225
  const existingVariationSlugs = fs6.readdirSync(variationsDir).filter((entry) => entry.endsWith(".ts") && entry !== "index.ts").map((entry) => entry.replace(/\.ts$/u, ""));
6865
7226
  const nextVariationSlugs = Array.from(new Set([...existingVariationSlugs, variationSlug])).sort();
6866
7227
  await fsp9.writeFile(variationsIndexPath, buildVariationIndexSource(nextVariationSlugs), "utf8");
6867
7228
  }
6868
7229
  async function writeBlockStyleRegistry(projectDir, blockSlug, styleSlug) {
6869
- const stylesDir = path13.join(projectDir, "src", "blocks", blockSlug, "styles");
6870
- const stylesIndexPath = path13.join(stylesDir, "index.ts");
7230
+ const stylesDir = path14.join(projectDir, "src", "blocks", blockSlug, "styles");
7231
+ const stylesIndexPath = path14.join(stylesDir, "index.ts");
6871
7232
  await fsp9.mkdir(stylesDir, { recursive: true });
6872
7233
  const existingStyleSlugs = fs6.readdirSync(stylesDir).filter((entry) => entry.endsWith(".ts") && entry !== "index.ts").map((entry) => entry.replace(/\.ts$/u, ""));
6873
7234
  const nextStyleSlugs = Array.from(new Set([...existingStyleSlugs, styleSlug])).sort();
6874
7235
  await fsp9.writeFile(stylesIndexPath, buildBlockStyleIndexSource(nextStyleSlugs), "utf8");
6875
7236
  }
6876
7237
  async function writeBlockTransformRegistry(projectDir, blockSlug, transformSlug) {
6877
- const transformsDir = path13.join(projectDir, "src", "blocks", blockSlug, "transforms");
6878
- const transformsIndexPath = path13.join(transformsDir, "index.ts");
7238
+ const transformsDir = path14.join(projectDir, "src", "blocks", blockSlug, "transforms");
7239
+ const transformsIndexPath = path14.join(transformsDir, "index.ts");
6879
7240
  await fsp9.mkdir(transformsDir, { recursive: true });
6880
7241
  const existingTransformSlugs = fs6.readdirSync(transformsDir).filter((entry) => entry.endsWith(".ts") && entry !== "index.ts").map((entry) => entry.replace(/\.ts$/u, ""));
6881
7242
  const nextTransformSlugs = Array.from(new Set([...existingTransformSlugs, transformSlug])).sort();
6882
7243
  await fsp9.writeFile(transformsIndexPath, buildBlockTransformIndexSource(nextTransformSlugs), "utf8");
6883
7244
  }
6884
7245
  function assertBlockStyleDoesNotExist(projectDir, blockSlug, styleSlug, inventory) {
6885
- const stylePath = path13.join(projectDir, "src", "blocks", blockSlug, "styles", `${styleSlug}.ts`);
7246
+ const stylePath = path14.join(projectDir, "src", "blocks", blockSlug, "styles", `${styleSlug}.ts`);
6886
7247
  if (fs6.existsSync(stylePath)) {
6887
- throw new Error(`A block style already exists at ${path13.relative(projectDir, stylePath)}. Choose a different name.`);
7248
+ throw new Error(`A block style already exists at ${path14.relative(projectDir, stylePath)}. Choose a different name.`);
6888
7249
  }
6889
7250
  if (inventory.blockStyles.some((entry) => entry.block === blockSlug && entry.slug === styleSlug)) {
6890
7251
  throw new Error(`A block style inventory entry already exists for ${blockSlug}/${styleSlug}. Choose a different name.`);
6891
7252
  }
6892
7253
  }
6893
7254
  function assertBlockTransformDoesNotExist(projectDir, blockSlug, transformSlug, inventory) {
6894
- const transformPath = path13.join(projectDir, "src", "blocks", blockSlug, "transforms", `${transformSlug}.ts`);
7255
+ const transformPath = path14.join(projectDir, "src", "blocks", blockSlug, "transforms", `${transformSlug}.ts`);
6895
7256
  if (fs6.existsSync(transformPath)) {
6896
- throw new Error(`A block transform already exists at ${path13.relative(projectDir, transformPath)}. Choose a different name.`);
7257
+ throw new Error(`A block transform already exists at ${path14.relative(projectDir, transformPath)}. Choose a different name.`);
6897
7258
  }
6898
7259
  if (inventory.blockTransforms.some((entry) => entry.block === blockSlug && entry.slug === transformSlug)) {
6899
7260
  throw new Error(`A block transform inventory entry already exists for ${blockSlug}/${transformSlug}. Choose a different name.`);
@@ -6939,11 +7300,11 @@ async function runAddVariationCommand({
6939
7300
  const inventory = readWorkspaceInventory(workspace.projectDir);
6940
7301
  resolveWorkspaceBlock(inventory, blockSlug);
6941
7302
  assertVariationDoesNotExist(workspace.projectDir, blockSlug, variationSlug, inventory);
6942
- const blockConfigPath = path13.join(workspace.projectDir, "scripts", "block-config.ts");
6943
- const blockIndexPath = path13.join(workspace.projectDir, "src", "blocks", blockSlug, "index.tsx");
6944
- const variationsDir = path13.join(workspace.projectDir, "src", "blocks", blockSlug, "variations");
6945
- const variationFilePath = path13.join(variationsDir, `${variationSlug}.ts`);
6946
- const variationsIndexPath = path13.join(variationsDir, "index.ts");
7303
+ const blockConfigPath = path14.join(workspace.projectDir, "scripts", "block-config.ts");
7304
+ const blockIndexPath = path14.join(workspace.projectDir, "src", "blocks", blockSlug, "index.tsx");
7305
+ const variationsDir = path14.join(workspace.projectDir, "src", "blocks", blockSlug, "variations");
7306
+ const variationFilePath = path14.join(variationsDir, `${variationSlug}.ts`);
7307
+ const variationsIndexPath = path14.join(variationsDir, "index.ts");
6947
7308
  const shouldRemoveVariationsDirOnRollback = !fs6.existsSync(variationsDir);
6948
7309
  const mutationSnapshot = {
6949
7310
  fileSources: await snapshotWorkspaceFiles([
@@ -6986,11 +7347,11 @@ async function runAddBlockStyleCommand({
6986
7347
  const inventory = readWorkspaceInventory(workspace.projectDir);
6987
7348
  resolveWorkspaceBlock(inventory, blockSlug);
6988
7349
  assertBlockStyleDoesNotExist(workspace.projectDir, blockSlug, styleSlug, inventory);
6989
- const blockConfigPath = path13.join(workspace.projectDir, "scripts", "block-config.ts");
6990
- const blockIndexPath = path13.join(workspace.projectDir, "src", "blocks", blockSlug, "index.tsx");
6991
- const stylesDir = path13.join(workspace.projectDir, "src", "blocks", blockSlug, "styles");
6992
- const styleFilePath = path13.join(stylesDir, `${styleSlug}.ts`);
6993
- const stylesIndexPath = path13.join(stylesDir, "index.ts");
7350
+ const blockConfigPath = path14.join(workspace.projectDir, "scripts", "block-config.ts");
7351
+ const blockIndexPath = path14.join(workspace.projectDir, "src", "blocks", blockSlug, "index.tsx");
7352
+ const stylesDir = path14.join(workspace.projectDir, "src", "blocks", blockSlug, "styles");
7353
+ const styleFilePath = path14.join(stylesDir, `${styleSlug}.ts`);
7354
+ const stylesIndexPath = path14.join(stylesDir, "index.ts");
6994
7355
  const shouldRemoveStylesDirOnRollback = !fs6.existsSync(stylesDir);
6995
7356
  const mutationSnapshot = {
6996
7357
  fileSources: await snapshotWorkspaceFiles([
@@ -7035,11 +7396,11 @@ async function runAddBlockTransformCommand({
7035
7396
  const inventory = readWorkspaceInventory(workspace.projectDir);
7036
7397
  resolveWorkspaceBlock(inventory, target.blockSlug);
7037
7398
  assertBlockTransformDoesNotExist(workspace.projectDir, target.blockSlug, transformSlug, inventory);
7038
- const blockConfigPath = path13.join(workspace.projectDir, "scripts", "block-config.ts");
7039
- const blockIndexPath = path13.join(workspace.projectDir, "src", "blocks", target.blockSlug, "index.tsx");
7040
- const transformsDir = path13.join(workspace.projectDir, "src", "blocks", target.blockSlug, "transforms");
7041
- const transformFilePath = path13.join(transformsDir, `${transformSlug}.ts`);
7042
- const transformsIndexPath = path13.join(transformsDir, "index.ts");
7399
+ const blockConfigPath = path14.join(workspace.projectDir, "scripts", "block-config.ts");
7400
+ const blockIndexPath = path14.join(workspace.projectDir, "src", "blocks", target.blockSlug, "index.tsx");
7401
+ const transformsDir = path14.join(workspace.projectDir, "src", "blocks", target.blockSlug, "transforms");
7402
+ const transformFilePath = path14.join(transformsDir, `${transformSlug}.ts`);
7403
+ const transformsIndexPath = path14.join(transformsDir, "index.ts");
7043
7404
  const shouldRemoveTransformsDirOnRollback = !fs6.existsSync(transformsDir);
7044
7405
  const mutationSnapshot = {
7045
7406
  fileSources: await snapshotWorkspaceFiles([
@@ -7101,7 +7462,7 @@ async function runAddHookedBlockCommand({
7101
7462
  throw new Error("`wp-typia add hooked-block` cannot hook a block relative to its own block name.");
7102
7463
  }
7103
7464
  const { blockJson, blockJsonPath } = readWorkspaceBlockJson(workspace.projectDir, blockSlug);
7104
- const blockJsonRelativePath = path13.relative(workspace.projectDir, blockJsonPath);
7465
+ const blockJsonRelativePath = path14.relative(workspace.projectDir, blockJsonPath);
7105
7466
  const blockHooks = getMutableBlockHooks(blockJson, blockJsonRelativePath);
7106
7467
  if (Object.prototype.hasOwnProperty.call(blockHooks, resolvedAnchorBlockName)) {
7107
7468
  throw new Error(`${blockJsonRelativePath} already defines a blockHooks entry for "${resolvedAnchorBlockName}".`);
@@ -7146,4 +7507,4 @@ export {
7146
7507
  ADD_BLOCK_TEMPLATE_IDS
7147
7508
  };
7148
7509
 
7149
- //# debugId=8932DC6C8A5BAD5564756E2164756E21
7510
+ //# debugId=FE993C1947A36DF464756E2164756E21