@wp-typia/project-tools 0.22.1 → 0.22.3
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.
- package/dist/runtime/built-in-block-code-templates/interactivity.d.ts +1 -1
- package/dist/runtime/built-in-block-code-templates/interactivity.js +4 -2
- package/dist/runtime/cli-add-shared.d.ts +49 -0
- package/dist/runtime/cli-add-shared.js +204 -71
- package/dist/runtime/cli-add-workspace-ability-scaffold.d.ts +5 -0
- package/dist/runtime/cli-add-workspace-ability-scaffold.js +392 -0
- package/dist/runtime/cli-add-workspace-ability-templates.d.ts +34 -0
- package/dist/runtime/cli-add-workspace-ability-templates.js +500 -0
- package/dist/runtime/cli-add-workspace-ability-types.d.ts +27 -0
- package/dist/runtime/cli-add-workspace-ability-types.js +14 -0
- package/dist/runtime/cli-add-workspace-ability.js +12 -852
- package/dist/runtime/cli-add-workspace-ai-scaffold.d.ts +21 -0
- package/dist/runtime/cli-add-workspace-ai-scaffold.js +91 -0
- package/dist/runtime/cli-add-workspace-ai-source-emitters.js +119 -1
- package/dist/runtime/cli-add-workspace-ai-templates.d.ts +4 -0
- package/dist/runtime/cli-add-workspace-ai-templates.js +605 -0
- package/dist/runtime/cli-add-workspace-ai.js +15 -465
- package/dist/runtime/cli-add-workspace-assets.js +7 -4
- package/dist/runtime/cli-add-workspace.js +1 -19
- package/dist/runtime/cli-doctor-workspace-bindings.d.ts +11 -0
- package/dist/runtime/cli-doctor-workspace-bindings.js +134 -0
- package/dist/runtime/cli-doctor-workspace-blocks.d.ts +11 -0
- package/dist/runtime/cli-doctor-workspace-blocks.js +504 -0
- package/dist/runtime/cli-doctor-workspace-features.d.ts +11 -0
- package/dist/runtime/cli-doctor-workspace-features.js +383 -0
- package/dist/runtime/cli-doctor-workspace-package.d.ts +18 -0
- package/dist/runtime/cli-doctor-workspace-package.js +59 -0
- package/dist/runtime/cli-doctor-workspace-shared.d.ts +69 -0
- package/dist/runtime/cli-doctor-workspace-shared.js +87 -0
- package/dist/runtime/cli-doctor-workspace.js +25 -1062
- package/dist/runtime/scaffold-compatibility.d.ts +2 -0
- package/dist/runtime/scaffold-compatibility.js +2 -0
- package/dist/runtime/typia-llm.d.ts +37 -2
- package/dist/runtime/typia-llm.js +240 -3
- package/dist/runtime/workspace-inventory.js +24 -0
- package/package.json +3 -3
|
@@ -1,392 +1,8 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import
|
|
3
|
-
import {
|
|
4
|
-
import { ensureBlockConfigCanAddRestManifests } from "./cli-add-block-legacy-validator.js";
|
|
5
|
-
import { buildAiFeatureConfigEntry, buildAiFeatureDataSource, buildAiFeatureSyncScriptSource, buildAiFeatureTypesSource, buildAiFeatureValidatorsSource, buildAiFeatureApiSource, } from "./cli-add-workspace-ai-source-emitters.js";
|
|
6
|
-
import { ensureAiFeatureBootstrapAnchors, ensureAiFeaturePackageScripts, ensureAiFeatureSyncProjectAnchors, ensureAiFeatureSyncRestAnchors, } from "./cli-add-workspace-ai-anchors.js";
|
|
7
|
-
import { syncAiFeatureRestArtifacts, syncAiFeatureSchemaArtifact, } from "./ai-feature-artifacts.js";
|
|
8
|
-
import { appendWorkspaceInventoryEntries, readWorkspaceInventory } from "./workspace-inventory.js";
|
|
1
|
+
import { assertAiFeatureDoesNotExist, assertValidGeneratedSlug, normalizeBlockSlug, resolveRestResourceNamespace, } from "./cli-add-shared.js";
|
|
2
|
+
import { scaffoldAiFeatureWorkspace } from "./cli-add-workspace-ai-scaffold.js";
|
|
3
|
+
import { readWorkspaceInventory } from "./workspace-inventory.js";
|
|
9
4
|
import { resolveWorkspaceProject } from "./workspace-project.js";
|
|
10
|
-
import { OPTIONAL_WORDPRESS_AI_CLIENT_COMPATIBILITY, resolveScaffoldCompatibilityPolicy,
|
|
11
|
-
import { quotePhpString } from "./php-utils.js";
|
|
12
|
-
import { toPascalCase, toTitleCase } from "./string-case.js";
|
|
13
|
-
function buildAiFeaturePhpSource(aiFeatureSlug, namespace, phpPrefix, textDomain) {
|
|
14
|
-
const aiFeatureTitle = toTitleCase(aiFeatureSlug);
|
|
15
|
-
const aiFeaturePhpId = aiFeatureSlug.replace(/-/g, "_");
|
|
16
|
-
const loadSchemaFunctionName = `${phpPrefix}_${aiFeaturePhpId}_load_ai_feature_schema`;
|
|
17
|
-
const loadAiSchemaFunctionName = `${phpPrefix}_${aiFeaturePhpId}_load_ai_response_schema`;
|
|
18
|
-
const normalizeSchemaFunctionName = `${phpPrefix}_${aiFeaturePhpId}_sanitize_ai_feature_schema`;
|
|
19
|
-
const validatePayloadFunctionName = `${phpPrefix}_${aiFeaturePhpId}_validate_ai_feature_payload`;
|
|
20
|
-
const canManageFunctionName = `${phpPrefix}_${aiFeaturePhpId}_can_manage_ai_feature`;
|
|
21
|
-
const buildPromptFunctionName = `${phpPrefix}_${aiFeaturePhpId}_build_ai_feature_prompt`;
|
|
22
|
-
const normalizeProviderTypeFunctionName = `${phpPrefix}_${aiFeaturePhpId}_normalize_provider_type`;
|
|
23
|
-
const buildTelemetryFunctionName = `${phpPrefix}_${aiFeaturePhpId}_build_ai_feature_telemetry`;
|
|
24
|
-
const isSupportedFunctionName = `${phpPrefix}_${aiFeaturePhpId}_is_ai_feature_supported`;
|
|
25
|
-
const adminNoticeFunctionName = `${phpPrefix}_${aiFeaturePhpId}_ai_feature_admin_notice`;
|
|
26
|
-
const handlerFunctionName = `${phpPrefix}_${aiFeaturePhpId}_handle_run_ai_feature`;
|
|
27
|
-
const registerRoutesFunctionName = `${phpPrefix}_${aiFeaturePhpId}_register_ai_feature_routes`;
|
|
28
|
-
return `<?php
|
|
29
|
-
if ( ! defined( 'ABSPATH' ) ) {
|
|
30
|
-
\treturn;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
if ( ! function_exists( '${loadSchemaFunctionName}' ) ) {
|
|
34
|
-
\tfunction ${loadSchemaFunctionName}( $schema_name ) {
|
|
35
|
-
\t\t$project_root = dirname( __DIR__, 2 );
|
|
36
|
-
\t\t$schema_path = $project_root . '/src/ai-features/${aiFeatureSlug}/api-schemas/' . $schema_name . '.schema.json';
|
|
37
|
-
\t\tif ( ! file_exists( $schema_path ) ) {
|
|
38
|
-
\t\t\treturn null;
|
|
39
|
-
\t\t}
|
|
40
|
-
|
|
41
|
-
\t\t$decoded = json_decode( file_get_contents( $schema_path ), true );
|
|
42
|
-
\t\treturn is_array( $decoded ) ? $decoded : null;
|
|
43
|
-
\t}
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
if ( ! function_exists( '${loadAiSchemaFunctionName}' ) ) {
|
|
47
|
-
\tfunction ${loadAiSchemaFunctionName}() {
|
|
48
|
-
\t\t$project_root = dirname( __DIR__, 2 );
|
|
49
|
-
\t\t$schema_path = $project_root . '/src/ai-features/${aiFeatureSlug}/ai-schemas/feature-result.ai.schema.json';
|
|
50
|
-
\t\tif ( ! file_exists( $schema_path ) ) {
|
|
51
|
-
\t\t\treturn null;
|
|
52
|
-
\t\t}
|
|
53
|
-
|
|
54
|
-
\t\t$decoded = json_decode( file_get_contents( $schema_path ), true );
|
|
55
|
-
\t\treturn is_array( $decoded ) ? $decoded : null;
|
|
56
|
-
\t}
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
if ( ! function_exists( '${normalizeSchemaFunctionName}' ) ) {
|
|
60
|
-
\tfunction ${normalizeSchemaFunctionName}( $schema ) {
|
|
61
|
-
\t\tif ( ! is_array( $schema ) ) {
|
|
62
|
-
\t\t\treturn $schema;
|
|
63
|
-
\t\t}
|
|
64
|
-
|
|
65
|
-
\t\tunset( $schema['$schema'], $schema['title'] );
|
|
66
|
-
|
|
67
|
-
\t\tif ( isset( $schema['properties'] ) && is_array( $schema['properties'] ) ) {
|
|
68
|
-
\t\t\tforeach ( $schema['properties'] as $key => $property_schema ) {
|
|
69
|
-
\t\t\t\t$schema['properties'][ $key ] = ${normalizeSchemaFunctionName}( $property_schema );
|
|
70
|
-
\t\t\t}
|
|
71
|
-
\t\t}
|
|
72
|
-
|
|
73
|
-
\t\tif ( isset( $schema['items'] ) && is_array( $schema['items'] ) ) {
|
|
74
|
-
\t\t\t$schema['items'] = ${normalizeSchemaFunctionName}( $schema['items'] );
|
|
75
|
-
\t\t}
|
|
76
|
-
|
|
77
|
-
\t\treturn $schema;
|
|
78
|
-
\t}
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
if ( ! function_exists( '${validatePayloadFunctionName}' ) ) {
|
|
82
|
-
\tfunction ${validatePayloadFunctionName}( $value, $schema_name, $param_name ) {
|
|
83
|
-
\t\t$schema = ${loadSchemaFunctionName}( $schema_name );
|
|
84
|
-
\t\tif ( ! is_array( $schema ) ) {
|
|
85
|
-
\t\t\treturn new WP_Error( 'missing_schema', 'Missing AI feature schema.', array( 'status' => 500 ) );
|
|
86
|
-
\t\t}
|
|
87
|
-
|
|
88
|
-
\t\t$rest_schema = ${normalizeSchemaFunctionName}( $schema );
|
|
89
|
-
\t\t$validation = rest_validate_value_from_schema( $value, $rest_schema, $param_name );
|
|
90
|
-
\t\tif ( is_wp_error( $validation ) ) {
|
|
91
|
-
\t\t\treturn $validation;
|
|
92
|
-
\t\t}
|
|
93
|
-
|
|
94
|
-
\t\treturn rest_sanitize_value_from_schema( $value, $rest_schema, $param_name );
|
|
95
|
-
\t}
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
if ( ! function_exists( '${canManageFunctionName}' ) ) {
|
|
99
|
-
\tfunction ${canManageFunctionName}() {
|
|
100
|
-
\t\treturn current_user_can( 'edit_posts' );
|
|
101
|
-
\t}
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
if ( ! function_exists( '${buildPromptFunctionName}' ) ) {
|
|
105
|
-
\tfunction ${buildPromptFunctionName}( array $payload ) {
|
|
106
|
-
\t\treturn sprintf(
|
|
107
|
-
\t\t\t'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',
|
|
108
|
-
\t\t\t${quotePhpString(aiFeatureTitle)},
|
|
109
|
-
\t\t\twp_json_encode( $payload )
|
|
110
|
-
\t\t);
|
|
111
|
-
\t}
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
if ( ! function_exists( '${normalizeProviderTypeFunctionName}' ) ) {
|
|
115
|
-
\tfunction ${normalizeProviderTypeFunctionName}( $provider_type ) {
|
|
116
|
-
\t\tif ( is_object( $provider_type ) && isset( $provider_type->value ) && is_string( $provider_type->value ) ) {
|
|
117
|
-
\t\t\treturn $provider_type->value;
|
|
118
|
-
\t\t}
|
|
119
|
-
|
|
120
|
-
\t\treturn is_string( $provider_type ) && '' !== $provider_type ? $provider_type : 'cloud';
|
|
121
|
-
\t}
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
if ( ! function_exists( '${buildTelemetryFunctionName}' ) ) {
|
|
125
|
-
\tfunction ${buildTelemetryFunctionName}( $result ) {
|
|
126
|
-
\t\tif (
|
|
127
|
-
\t\t\t! is_object( $result ) ||
|
|
128
|
-
\t\t\t! method_exists( $result, 'getId' ) ||
|
|
129
|
-
\t\t\t! method_exists( $result, 'getModelMetadata' ) ||
|
|
130
|
-
\t\t\t! method_exists( $result, 'getProviderMetadata' ) ||
|
|
131
|
-
\t\t\t! method_exists( $result, 'getTokenUsage' )
|
|
132
|
-
\t\t) {
|
|
133
|
-
\t\t\treturn new WP_Error(
|
|
134
|
-
\t\t\t\t'ai_client_result_shape',
|
|
135
|
-
\t\t\t\t'The current WordPress AI Client result object is missing telemetry helpers.',
|
|
136
|
-
\t\t\t\tarray( 'status' => 502 )
|
|
137
|
-
\t\t\t);
|
|
138
|
-
\t\t}
|
|
139
|
-
|
|
140
|
-
\t\t$model_metadata = $result->getModelMetadata();
|
|
141
|
-
\t\t$provider_metadata = $result->getProviderMetadata();
|
|
142
|
-
\t\t$token_usage = $result->getTokenUsage();
|
|
143
|
-
|
|
144
|
-
\t\tif (
|
|
145
|
-
\t\t\t! is_object( $model_metadata ) ||
|
|
146
|
-
\t\t\t! method_exists( $model_metadata, 'getId' ) ||
|
|
147
|
-
\t\t\t! method_exists( $model_metadata, 'getName' ) ||
|
|
148
|
-
\t\t\t! is_object( $provider_metadata ) ||
|
|
149
|
-
\t\t\t! method_exists( $provider_metadata, 'getId' ) ||
|
|
150
|
-
\t\t\t! method_exists( $provider_metadata, 'getName' ) ||
|
|
151
|
-
\t\t\t! method_exists( $provider_metadata, 'getType' ) ||
|
|
152
|
-
\t\t\t! is_object( $token_usage ) ||
|
|
153
|
-
\t\t\t! method_exists( $token_usage, 'getCompletionTokens' ) ||
|
|
154
|
-
\t\t\t! method_exists( $token_usage, 'getPromptTokens' ) ||
|
|
155
|
-
\t\t\t! method_exists( $token_usage, 'getTotalTokens' )
|
|
156
|
-
\t\t) {
|
|
157
|
-
\t\t\treturn new WP_Error(
|
|
158
|
-
\t\t\t\t'ai_client_result_shape',
|
|
159
|
-
\t\t\t\t'The current WordPress AI Client telemetry objects are missing expected getters.',
|
|
160
|
-
\t\t\t\tarray( 'status' => 502 )
|
|
161
|
-
\t\t\t);
|
|
162
|
-
\t\t}
|
|
163
|
-
|
|
164
|
-
\t\t$telemetry = array(
|
|
165
|
-
\t\t\t'modelId' => (string) $model_metadata->getId(),
|
|
166
|
-
\t\t\t'modelName' => (string) $model_metadata->getName(),
|
|
167
|
-
\t\t\t'providerId' => (string) $provider_metadata->getId(),
|
|
168
|
-
\t\t\t'providerName' => (string) $provider_metadata->getName(),
|
|
169
|
-
\t\t\t'providerType' => ${normalizeProviderTypeFunctionName}( $provider_metadata->getType() ),
|
|
170
|
-
\t\t\t'resultId' => (string) $result->getId(),
|
|
171
|
-
\t\t\t'tokenUsage' => array(
|
|
172
|
-
\t\t\t\t'completionTokens' => (int) $token_usage->getCompletionTokens(),
|
|
173
|
-
\t\t\t\t'promptTokens' => (int) $token_usage->getPromptTokens(),
|
|
174
|
-
\t\t\t\t'totalTokens' => (int) $token_usage->getTotalTokens(),
|
|
175
|
-
\t\t\t),
|
|
176
|
-
\t\t);
|
|
177
|
-
|
|
178
|
-
\t\tif ( method_exists( $token_usage, 'getThoughtTokens' ) ) {
|
|
179
|
-
\t\t\t$thought_tokens = $token_usage->getThoughtTokens();
|
|
180
|
-
\t\t\tif ( null !== $thought_tokens ) {
|
|
181
|
-
\t\t\t\t$telemetry['tokenUsage']['thoughtTokens'] = (int) $thought_tokens;
|
|
182
|
-
\t\t\t}
|
|
183
|
-
\t\t}
|
|
184
|
-
|
|
185
|
-
\t\treturn $telemetry;
|
|
186
|
-
\t}
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
if ( ! function_exists( '${isSupportedFunctionName}' ) ) {
|
|
190
|
-
\tfunction ${isSupportedFunctionName}() {
|
|
191
|
-
\t\tstatic $is_supported = null;
|
|
192
|
-
\t\tif ( null !== $is_supported ) {
|
|
193
|
-
\t\t\treturn $is_supported;
|
|
194
|
-
\t\t}
|
|
195
|
-
|
|
196
|
-
\t\tif ( ! function_exists( 'wp_ai_client_prompt' ) ) {
|
|
197
|
-
\t\t\t$is_supported = false;
|
|
198
|
-
\t\t\treturn $is_supported;
|
|
199
|
-
\t\t}
|
|
200
|
-
|
|
201
|
-
\t\t$schema = ${loadAiSchemaFunctionName}();
|
|
202
|
-
\t\tif ( ! is_array( $schema ) ) {
|
|
203
|
-
\t\t\t$is_supported = false;
|
|
204
|
-
\t\t\treturn $is_supported;
|
|
205
|
-
\t\t}
|
|
206
|
-
|
|
207
|
-
\t\t$prompt = wp_ai_client_prompt( 'AI feature support probe.' );
|
|
208
|
-
\t\tif ( ! is_object( $prompt ) || ! method_exists( $prompt, 'as_json_response' ) ) {
|
|
209
|
-
\t\t\t$is_supported = false;
|
|
210
|
-
\t\t\treturn $is_supported;
|
|
211
|
-
\t\t}
|
|
212
|
-
|
|
213
|
-
\t\t$structured_prompt = $prompt->as_json_response( $schema );
|
|
214
|
-
\t\tif ( ! is_object( $structured_prompt ) ) {
|
|
215
|
-
\t\t\t$is_supported = false;
|
|
216
|
-
\t\t\treturn $is_supported;
|
|
217
|
-
\t\t}
|
|
218
|
-
|
|
219
|
-
\t\tif ( method_exists( $structured_prompt, 'is_supported_for_text_generation' ) ) {
|
|
220
|
-
\t\t\t$is_supported = (bool) $structured_prompt->is_supported_for_text_generation();
|
|
221
|
-
\t\t\treturn $is_supported;
|
|
222
|
-
\t\t}
|
|
223
|
-
|
|
224
|
-
\t\t$is_supported = method_exists( $structured_prompt, 'generate_text_result' );
|
|
225
|
-
\t\treturn $is_supported;
|
|
226
|
-
\t}
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
if ( ! function_exists( '${adminNoticeFunctionName}' ) ) {
|
|
230
|
-
\tfunction ${adminNoticeFunctionName}() {
|
|
231
|
-
\t\tif ( ! current_user_can( 'manage_options' ) || ${isSupportedFunctionName}() ) {
|
|
232
|
-
\t\t\treturn;
|
|
233
|
-
\t\t}
|
|
234
|
-
|
|
235
|
-
\t\t$message = sprintf(
|
|
236
|
-
\t\t\t/* translators: %s: AI feature name. */
|
|
237
|
-
\t\t\t__( '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)} ),
|
|
238
|
-
\t\t\t${quotePhpString(aiFeatureTitle)}
|
|
239
|
-
\t\t);
|
|
240
|
-
\t\tprintf( '<div class="notice notice-warning"><p>%s</p></div>', esc_html( $message ) );
|
|
241
|
-
\t}
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
if ( ! function_exists( '${handlerFunctionName}' ) ) {
|
|
245
|
-
\tfunction ${handlerFunctionName}( WP_REST_Request $request ) {
|
|
246
|
-
\t\t$payload = ${validatePayloadFunctionName}( $request->get_json_params(), 'feature-request', 'body' );
|
|
247
|
-
\t\tif ( is_wp_error( $payload ) ) {
|
|
248
|
-
\t\t\treturn $payload;
|
|
249
|
-
\t\t}
|
|
250
|
-
|
|
251
|
-
\t\tif ( ! ${isSupportedFunctionName}() ) {
|
|
252
|
-
\t\t\treturn new WP_Error(
|
|
253
|
-
\t\t\t\t'ai_client_unavailable',
|
|
254
|
-
\t\t\t\t'The WordPress AI Client is unavailable or does not support this feature endpoint.',
|
|
255
|
-
\t\t\t\tarray( 'status' => 501 )
|
|
256
|
-
\t\t\t);
|
|
257
|
-
\t\t}
|
|
258
|
-
|
|
259
|
-
\t\t$ai_schema = ${loadAiSchemaFunctionName}();
|
|
260
|
-
\t\tif ( ! is_array( $ai_schema ) ) {
|
|
261
|
-
\t\t\treturn new WP_Error(
|
|
262
|
-
\t\t\t\t'ai_client_schema_missing',
|
|
263
|
-
\t\t\t\t'The generated AI response schema is missing for this feature endpoint.',
|
|
264
|
-
\t\t\t\tarray( 'status' => 500 )
|
|
265
|
-
\t\t\t);
|
|
266
|
-
\t\t}
|
|
267
|
-
|
|
268
|
-
\t\t$prompt = wp_ai_client_prompt( ${buildPromptFunctionName}( $payload ) );
|
|
269
|
-
\t\tif ( ! is_object( $prompt ) ) {
|
|
270
|
-
\t\t\treturn new WP_Error(
|
|
271
|
-
\t\t\t\t'ai_client_unavailable',
|
|
272
|
-
\t\t\t\t'The WordPress AI Client prompt builder is unavailable on this site.',
|
|
273
|
-
\t\t\t\tarray( 'status' => 501 )
|
|
274
|
-
\t\t\t);
|
|
275
|
-
\t\t}
|
|
276
|
-
|
|
277
|
-
\t\tif ( method_exists( $prompt, 'using_temperature' ) ) {
|
|
278
|
-
\t\t\t$prompt = $prompt->using_temperature( 0.2 );
|
|
279
|
-
\t\t}
|
|
280
|
-
\t\tif ( ! method_exists( $prompt, 'as_json_response' ) ) {
|
|
281
|
-
\t\t\treturn new WP_Error(
|
|
282
|
-
\t\t\t\t'ai_client_unavailable',
|
|
283
|
-
\t\t\t\t'The current WordPress AI Client does not expose as_json_response().',
|
|
284
|
-
\t\t\t\tarray( 'status' => 501 )
|
|
285
|
-
\t\t\t);
|
|
286
|
-
\t\t}
|
|
287
|
-
|
|
288
|
-
\t\t$structured_prompt = $prompt->as_json_response( $ai_schema );
|
|
289
|
-
\t\tif ( ! is_object( $structured_prompt ) ) {
|
|
290
|
-
\t\t\treturn new WP_Error(
|
|
291
|
-
\t\t\t\t'ai_client_unavailable',
|
|
292
|
-
\t\t\t\t'The current WordPress AI Client could not prepare a structured-output prompt.',
|
|
293
|
-
\t\t\t\tarray( 'status' => 501 )
|
|
294
|
-
\t\t\t);
|
|
295
|
-
\t\t}
|
|
296
|
-
|
|
297
|
-
\t\tif (
|
|
298
|
-
\t\t\tmethod_exists( $structured_prompt, 'is_supported_for_text_generation' ) &&
|
|
299
|
-
\t\t\t! $structured_prompt->is_supported_for_text_generation()
|
|
300
|
-
\t\t) {
|
|
301
|
-
\t\t\treturn new WP_Error(
|
|
302
|
-
\t\t\t\t'ai_client_unavailable',
|
|
303
|
-
\t\t\t\t'The current WordPress AI Client provider or model does not support this structured-output feature.',
|
|
304
|
-
\t\t\t\tarray( 'status' => 501 )
|
|
305
|
-
\t\t\t);
|
|
306
|
-
\t\t}
|
|
307
|
-
\t\tif ( ! method_exists( $structured_prompt, 'generate_text_result' ) ) {
|
|
308
|
-
\t\t\treturn new WP_Error(
|
|
309
|
-
\t\t\t\t'ai_client_unavailable',
|
|
310
|
-
\t\t\t\t'The current WordPress AI Client does not expose generate_text_result() after as_json_response().',
|
|
311
|
-
\t\t\t\tarray( 'status' => 501 )
|
|
312
|
-
\t\t\t);
|
|
313
|
-
\t\t}
|
|
314
|
-
|
|
315
|
-
\t\t$result = $structured_prompt->generate_text_result();
|
|
316
|
-
\t\tif ( is_wp_error( $result ) ) {
|
|
317
|
-
\t\t\treturn $result;
|
|
318
|
-
\t\t}
|
|
319
|
-
\t\tif ( ! is_object( $result ) || ! method_exists( $result, 'toText' ) ) {
|
|
320
|
-
\t\t\treturn new WP_Error(
|
|
321
|
-
\t\t\t\t'ai_client_result_shape',
|
|
322
|
-
\t\t\t\t'The current WordPress AI Client result does not expose toText().',
|
|
323
|
-
\t\t\t\tarray( 'status' => 502 )
|
|
324
|
-
\t\t\t);
|
|
325
|
-
\t\t}
|
|
326
|
-
|
|
327
|
-
\t\t$decoded_result = json_decode( $result->toText(), true );
|
|
328
|
-
\t\tif ( ! is_array( $decoded_result ) ) {
|
|
329
|
-
\t\t\treturn new WP_Error(
|
|
330
|
-
\t\t\t\t'ai_client_invalid_json',
|
|
331
|
-
\t\t\t\t'The AI feature response did not decode to a JSON object.',
|
|
332
|
-
\t\t\t\tarray( 'status' => 502 )
|
|
333
|
-
\t\t\t);
|
|
334
|
-
\t\t}
|
|
335
|
-
|
|
336
|
-
\t\t$normalized_result = ${validatePayloadFunctionName}( $decoded_result, 'feature-result', 'result' );
|
|
337
|
-
\t\tif ( is_wp_error( $normalized_result ) ) {
|
|
338
|
-
\t\t\treturn new WP_Error(
|
|
339
|
-
\t\t\t\t'ai_client_invalid_response',
|
|
340
|
-
\t\t\t\t$normalized_result->get_error_message(),
|
|
341
|
-
\t\t\t\tarray( 'status' => 502 )
|
|
342
|
-
\t\t\t);
|
|
343
|
-
\t\t}
|
|
344
|
-
|
|
345
|
-
\t\t$telemetry = ${buildTelemetryFunctionName}( $result );
|
|
346
|
-
\t\tif ( is_wp_error( $telemetry ) ) {
|
|
347
|
-
\t\t\treturn $telemetry;
|
|
348
|
-
\t\t}
|
|
349
|
-
|
|
350
|
-
\t\t$response = ${validatePayloadFunctionName}(
|
|
351
|
-
\t\t\tarray(
|
|
352
|
-
\t\t\t\t'result' => $normalized_result,
|
|
353
|
-
\t\t\t\t'telemetry' => $telemetry,
|
|
354
|
-
\t\t\t),
|
|
355
|
-
\t\t\t'feature-response',
|
|
356
|
-
\t\t\t'response'
|
|
357
|
-
\t\t);
|
|
358
|
-
\t\tif ( is_wp_error( $response ) ) {
|
|
359
|
-
\t\t\treturn new WP_Error(
|
|
360
|
-
\t\t\t\t'ai_client_invalid_response',
|
|
361
|
-
\t\t\t\t$response->get_error_message(),
|
|
362
|
-
\t\t\t\tarray( 'status' => 502 )
|
|
363
|
-
\t\t\t);
|
|
364
|
-
\t\t}
|
|
365
|
-
|
|
366
|
-
\t\treturn rest_ensure_response( $response );
|
|
367
|
-
\t}
|
|
368
|
-
}
|
|
369
|
-
|
|
370
|
-
if ( ! function_exists( '${registerRoutesFunctionName}' ) ) {
|
|
371
|
-
\tfunction ${registerRoutesFunctionName}() {
|
|
372
|
-
\t\tregister_rest_route(
|
|
373
|
-
\t\t\t${quotePhpString(namespace)},
|
|
374
|
-
\t\t\t'/ai/${aiFeatureSlug}',
|
|
375
|
-
\t\t\tarray(
|
|
376
|
-
\t\t\t\tarray(
|
|
377
|
-
\t\t\t\t\t'methods' => WP_REST_Server::CREATABLE,
|
|
378
|
-
\t\t\t\t\t'callback' => '${handlerFunctionName}',
|
|
379
|
-
\t\t\t\t\t'permission_callback' => '${canManageFunctionName}',
|
|
380
|
-
\t\t\t\t)
|
|
381
|
-
\t\t\t)
|
|
382
|
-
\t\t);
|
|
383
|
-
\t}
|
|
384
|
-
}
|
|
385
|
-
|
|
386
|
-
add_action( 'admin_notices', '${adminNoticeFunctionName}' );
|
|
387
|
-
add_action( 'rest_api_init', '${registerRoutesFunctionName}' );
|
|
388
|
-
`;
|
|
389
|
-
}
|
|
5
|
+
import { OPTIONAL_WORDPRESS_AI_CLIENT_COMPATIBILITY, resolveScaffoldCompatibilityPolicy, } from "./scaffold-compatibility.js";
|
|
390
6
|
/**
|
|
391
7
|
* Scaffold a workspace-level server-only AI feature endpoint and synchronize
|
|
392
8
|
* its typed REST plus AI-schema artifacts.
|
|
@@ -401,82 +17,16 @@ export async function runAddAiFeatureCommand({ aiFeatureName, cwd = process.cwd(
|
|
|
401
17
|
const compatibilityPolicy = resolveScaffoldCompatibilityPolicy(OPTIONAL_WORDPRESS_AI_CLIENT_COMPATIBILITY);
|
|
402
18
|
const inventory = readWorkspaceInventory(workspace.projectDir);
|
|
403
19
|
assertAiFeatureDoesNotExist(workspace.projectDir, aiFeatureSlug, inventory);
|
|
404
|
-
const
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
const phpFilePath = path.join(workspace.projectDir, "inc", "ai-features", `${aiFeatureSlug}.php`);
|
|
416
|
-
const mutationSnapshot = {
|
|
417
|
-
fileSources: await snapshotWorkspaceFiles([
|
|
418
|
-
blockConfigPath,
|
|
419
|
-
bootstrapPath,
|
|
420
|
-
packageJsonPath,
|
|
421
|
-
syncAiScriptPath,
|
|
422
|
-
syncProjectScriptPath,
|
|
423
|
-
syncRestScriptPath,
|
|
424
|
-
]),
|
|
425
|
-
snapshotDirs: [],
|
|
426
|
-
targetPaths: [aiFeatureDir, phpFilePath, syncAiScriptPath],
|
|
20
|
+
const scaffoldResult = await scaffoldAiFeatureWorkspace({
|
|
21
|
+
aiFeatureSlug,
|
|
22
|
+
compatibilityPolicy,
|
|
23
|
+
namespace: resolvedNamespace,
|
|
24
|
+
workspace,
|
|
25
|
+
});
|
|
26
|
+
return {
|
|
27
|
+
aiFeatureSlug,
|
|
28
|
+
namespace: resolvedNamespace,
|
|
29
|
+
projectDir: workspace.projectDir,
|
|
30
|
+
warnings: scaffoldResult.warnings,
|
|
427
31
|
};
|
|
428
|
-
try {
|
|
429
|
-
await fsp.mkdir(aiFeatureDir, { recursive: true });
|
|
430
|
-
await fsp.mkdir(path.dirname(phpFilePath), { recursive: true });
|
|
431
|
-
await ensureAiFeatureBootstrapAnchors(workspace);
|
|
432
|
-
await patchFile(bootstrapPath, (source) => updatePluginHeaderCompatibility(source, compatibilityPolicy));
|
|
433
|
-
const packageScriptChanges = await ensureAiFeaturePackageScripts(workspace);
|
|
434
|
-
await ensureAiFeatureSyncProjectAnchors(workspace);
|
|
435
|
-
await ensureAiFeatureSyncRestAnchors(workspace);
|
|
436
|
-
await fsp.writeFile(syncAiScriptPath, buildAiFeatureSyncScriptSource(), "utf8");
|
|
437
|
-
await fsp.writeFile(typesFilePath, buildAiFeatureTypesSource(aiFeatureSlug), "utf8");
|
|
438
|
-
await fsp.writeFile(validatorsFilePath, buildAiFeatureValidatorsSource(aiFeatureSlug), "utf8");
|
|
439
|
-
await fsp.writeFile(apiFilePath, buildAiFeatureApiSource(aiFeatureSlug), "utf8");
|
|
440
|
-
await fsp.writeFile(dataFilePath, buildAiFeatureDataSource(aiFeatureSlug), "utf8");
|
|
441
|
-
await fsp.writeFile(phpFilePath, buildAiFeaturePhpSource(aiFeatureSlug, resolvedNamespace, workspace.workspace.phpPrefix, workspace.workspace.textDomain), "utf8");
|
|
442
|
-
const pascalCase = toPascalCase(aiFeatureSlug);
|
|
443
|
-
await syncAiFeatureRestArtifacts({
|
|
444
|
-
clientFile: `src/ai-features/${aiFeatureSlug}/api-client.ts`,
|
|
445
|
-
outputDir: path.join("src", "ai-features", aiFeatureSlug),
|
|
446
|
-
projectDir: workspace.projectDir,
|
|
447
|
-
typesFile: `src/ai-features/${aiFeatureSlug}/api-types.ts`,
|
|
448
|
-
validatorsFile: `src/ai-features/${aiFeatureSlug}/api-validators.ts`,
|
|
449
|
-
variables: {
|
|
450
|
-
namespace: resolvedNamespace,
|
|
451
|
-
pascalCase,
|
|
452
|
-
slugKebabCase: aiFeatureSlug,
|
|
453
|
-
title: toTitleCase(aiFeatureSlug),
|
|
454
|
-
},
|
|
455
|
-
});
|
|
456
|
-
await syncAiFeatureSchemaArtifact({
|
|
457
|
-
aiSchemaFile: `src/ai-features/${aiFeatureSlug}/ai-schemas/feature-result.ai.schema.json`,
|
|
458
|
-
outputDir: path.join("src", "ai-features", aiFeatureSlug),
|
|
459
|
-
projectDir: workspace.projectDir,
|
|
460
|
-
});
|
|
461
|
-
await appendWorkspaceInventoryEntries(workspace.projectDir, {
|
|
462
|
-
aiFeatureEntries: [
|
|
463
|
-
buildAiFeatureConfigEntry(aiFeatureSlug, resolvedNamespace),
|
|
464
|
-
],
|
|
465
|
-
transformSource: ensureBlockConfigCanAddRestManifests,
|
|
466
|
-
});
|
|
467
|
-
return {
|
|
468
|
-
aiFeatureSlug,
|
|
469
|
-
namespace: resolvedNamespace,
|
|
470
|
-
projectDir: workspace.projectDir,
|
|
471
|
-
warnings: packageScriptChanges.addedProjectToolsDependency
|
|
472
|
-
? [
|
|
473
|
-
"Added `@wp-typia/project-tools` to devDependencies for `sync-ai`. If this workspace was already installed, rerun your package manager install command before the first `wp-typia sync ai`.",
|
|
474
|
-
]
|
|
475
|
-
: [],
|
|
476
|
-
};
|
|
477
|
-
}
|
|
478
|
-
catch (error) {
|
|
479
|
-
await rollbackWorkspaceMutation(mutationSnapshot);
|
|
480
|
-
throw error;
|
|
481
|
-
}
|
|
482
32
|
}
|
|
@@ -8,6 +8,7 @@ import { readWorkspaceInventory, appendWorkspaceInventoryEntries, } from "./work
|
|
|
8
8
|
import { toPascalCase, toTitleCase } from "./string-case.js";
|
|
9
9
|
import { findPhpFunctionRange, hasPhpFunctionDefinition, quotePhpString, replacePhpFunctionDefinition, } from "./php-utils.js";
|
|
10
10
|
import { assertBindingSourceDoesNotExist, assertEditorPluginDoesNotExist, assertPatternDoesNotExist, assertValidEditorPluginSlot, assertValidGeneratedSlug, getWorkspaceBootstrapPath, normalizeBlockSlug, patchFile, quoteTsString, resolveWorkspaceBlock, rollbackWorkspaceMutation, snapshotWorkspaceFiles, } from "./cli-add-shared.js";
|
|
11
|
+
import { normalizeOptionalCliString } from "./cli-validation.js";
|
|
11
12
|
const PATTERN_BOOTSTRAP_CATEGORY = "register_block_pattern_category";
|
|
12
13
|
const BINDING_SOURCE_SERVER_GLOB = "/src/bindings/*/server.php";
|
|
13
14
|
const BINDING_SOURCE_EDITOR_SCRIPT = "build/bindings/index.js";
|
|
@@ -225,8 +226,10 @@ registerBlockBindingsSource( {
|
|
|
225
226
|
`;
|
|
226
227
|
}
|
|
227
228
|
function resolveBindingTarget(options, namespace) {
|
|
228
|
-
const
|
|
229
|
-
const
|
|
229
|
+
const blockName = normalizeOptionalCliString(options.blockName);
|
|
230
|
+
const attributeName = normalizeOptionalCliString(options.attributeName);
|
|
231
|
+
const hasBlock = blockName !== undefined;
|
|
232
|
+
const hasAttribute = attributeName !== undefined;
|
|
230
233
|
if (!hasBlock && !hasAttribute) {
|
|
231
234
|
return undefined;
|
|
232
235
|
}
|
|
@@ -234,8 +237,8 @@ function resolveBindingTarget(options, namespace) {
|
|
|
234
237
|
throw new Error("`wp-typia add binding-source` requires --block and --attribute to be provided together.");
|
|
235
238
|
}
|
|
236
239
|
return {
|
|
237
|
-
attributeName: assertValidBindingAttributeName(
|
|
238
|
-
blockSlug: resolveBindingTargetBlockSlug(
|
|
240
|
+
attributeName: assertValidBindingAttributeName(attributeName ?? ""),
|
|
241
|
+
blockSlug: resolveBindingTargetBlockSlug(blockName ?? "", namespace),
|
|
239
242
|
};
|
|
240
243
|
}
|
|
241
244
|
function formatBindingAttributeTypeMember(attributeName) {
|
|
@@ -4,7 +4,7 @@ import path from "node:path";
|
|
|
4
4
|
import { resolveWorkspaceProject } from "./workspace-project.js";
|
|
5
5
|
import { appendWorkspaceInventoryEntries, readWorkspaceInventory } from "./workspace-inventory.js";
|
|
6
6
|
import { toKebabCase, toSnakeCase, toTitleCase } from "./string-case.js";
|
|
7
|
-
import { assertValidGeneratedSlug, assertValidHookAnchor, assertValidHookedBlockPosition, assertVariationDoesNotExist, getMutableBlockHooks, normalizeBlockSlug, patchFile, quoteTsString, readWorkspaceBlockJson, resolveWorkspaceBlock, rollbackWorkspaceMutation, snapshotWorkspaceFiles, } from "./cli-add-shared.js";
|
|
7
|
+
import { assertBlockStyleDoesNotExist, assertBlockTransformDoesNotExist, assertValidGeneratedSlug, assertValidHookAnchor, assertValidHookedBlockPosition, assertVariationDoesNotExist, getMutableBlockHooks, normalizeBlockSlug, patchFile, quoteTsString, readWorkspaceBlockJson, resolveWorkspaceBlock, rollbackWorkspaceMutation, snapshotWorkspaceFiles, } from "./cli-add-shared.js";
|
|
8
8
|
const VARIATIONS_IMPORT_LINE = "import { registerWorkspaceVariations } from './variations';";
|
|
9
9
|
const VARIATIONS_IMPORT_PATTERN = /^\s*import\s*\{\s*registerWorkspaceVariations\s*\}\s*from\s*["']\.\/variations["']\s*;?\s*$/mu;
|
|
10
10
|
const VARIATIONS_CALL_LINE = "registerWorkspaceVariations();";
|
|
@@ -488,24 +488,6 @@ async function writeBlockTransformRegistry(projectDir, blockSlug, transformSlug)
|
|
|
488
488
|
const nextTransformSlugs = Array.from(new Set([...existingTransformSlugs, transformSlug])).sort();
|
|
489
489
|
await fsp.writeFile(transformsIndexPath, buildBlockTransformIndexSource(nextTransformSlugs), "utf8");
|
|
490
490
|
}
|
|
491
|
-
function assertBlockStyleDoesNotExist(projectDir, blockSlug, styleSlug, inventory) {
|
|
492
|
-
const stylePath = path.join(projectDir, "src", "blocks", blockSlug, "styles", `${styleSlug}.ts`);
|
|
493
|
-
if (fs.existsSync(stylePath)) {
|
|
494
|
-
throw new Error(`A block style already exists at ${path.relative(projectDir, stylePath)}. Choose a different name.`);
|
|
495
|
-
}
|
|
496
|
-
if (inventory.blockStyles.some((entry) => entry.block === blockSlug && entry.slug === styleSlug)) {
|
|
497
|
-
throw new Error(`A block style inventory entry already exists for ${blockSlug}/${styleSlug}. Choose a different name.`);
|
|
498
|
-
}
|
|
499
|
-
}
|
|
500
|
-
function assertBlockTransformDoesNotExist(projectDir, blockSlug, transformSlug, inventory) {
|
|
501
|
-
const transformPath = path.join(projectDir, "src", "blocks", blockSlug, "transforms", `${transformSlug}.ts`);
|
|
502
|
-
if (fs.existsSync(transformPath)) {
|
|
503
|
-
throw new Error(`A block transform already exists at ${path.relative(projectDir, transformPath)}. Choose a different name.`);
|
|
504
|
-
}
|
|
505
|
-
if (inventory.blockTransforms.some((entry) => entry.block === blockSlug && entry.slug === transformSlug)) {
|
|
506
|
-
throw new Error(`A block transform inventory entry already exists for ${blockSlug}/${transformSlug}. Choose a different name.`);
|
|
507
|
-
}
|
|
508
|
-
}
|
|
509
491
|
function assertFullBlockName(blockName, flagName) {
|
|
510
492
|
const trimmed = blockName.trim();
|
|
511
493
|
if (!trimmed) {
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { DoctorCheck } from "./cli-doctor.js";
|
|
2
|
+
import type { WorkspaceInventory } from "./workspace-inventory.js";
|
|
3
|
+
import type { WorkspaceProject } from "./workspace-project.js";
|
|
4
|
+
/**
|
|
5
|
+
* Collect workspace doctor checks for extracted binding-source diagnostics.
|
|
6
|
+
*
|
|
7
|
+
* @param workspace Resolved workspace metadata and filesystem paths.
|
|
8
|
+
* @param inventory Parsed workspace inventory from `scripts/block-config.ts`.
|
|
9
|
+
* @returns Ordered `DoctorCheck[]` rows for binding bootstrap, index, and target checks.
|
|
10
|
+
*/
|
|
11
|
+
export declare function getWorkspaceBindingDoctorChecks(workspace: WorkspaceProject, inventory: WorkspaceInventory): DoctorCheck[];
|