@wp-typia/project-tools 0.19.3 → 0.20.0
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/README.md +23 -0
- package/dist/runtime/ability-spec.d.ts +90 -0
- package/dist/runtime/ability-spec.js +51 -0
- package/dist/runtime/ai-artifacts.d.ts +39 -0
- package/dist/runtime/ai-artifacts.js +68 -0
- package/dist/runtime/ai-feature-artifacts.d.ts +85 -0
- package/dist/runtime/ai-feature-artifacts.js +139 -0
- package/dist/runtime/ai-feature-capability.d.ts +114 -0
- package/dist/runtime/ai-feature-capability.js +150 -0
- package/dist/runtime/block-generator-service-spec.js +6 -0
- package/dist/runtime/cli-add-shared.d.ts +32 -1
- package/dist/runtime/cli-add-shared.js +44 -0
- package/dist/runtime/cli-add-workspace-ability.d.ts +8 -0
- package/dist/runtime/cli-add-workspace-ability.js +810 -0
- package/dist/runtime/cli-add-workspace-ai-anchors.d.ts +22 -0
- package/dist/runtime/cli-add-workspace-ai-anchors.js +277 -0
- package/dist/runtime/cli-add-workspace-ai-source-emitters.d.ts +28 -0
- package/dist/runtime/cli-add-workspace-ai-source-emitters.js +346 -0
- package/dist/runtime/cli-add-workspace-ai.d.ts +14 -0
- package/dist/runtime/cli-add-workspace-ai.js +484 -0
- package/dist/runtime/cli-add-workspace.d.ts +10 -0
- package/dist/runtime/cli-add-workspace.js +10 -0
- package/dist/runtime/cli-add.d.ts +1 -1
- package/dist/runtime/cli-add.js +1 -1
- package/dist/runtime/cli-core.d.ts +3 -1
- package/dist/runtime/cli-core.js +3 -1
- package/dist/runtime/cli-doctor-workspace.js +140 -1
- package/dist/runtime/cli-help.js +4 -0
- package/dist/runtime/index.d.ts +3 -1
- package/dist/runtime/index.js +2 -1
- package/dist/runtime/scaffold-compatibility.d.ts +65 -0
- package/dist/runtime/scaffold-compatibility.js +152 -0
- package/dist/runtime/scaffold-template-variable-groups.d.ts +2 -0
- package/dist/runtime/scaffold-template-variables.js +6 -0
- package/dist/runtime/scaffold.d.ts +3 -0
- package/dist/runtime/typia-llm.d.ts +213 -0
- package/dist/runtime/typia-llm.js +348 -0
- package/dist/runtime/wordpress-ai.d.ts +122 -0
- package/dist/runtime/wordpress-ai.js +177 -0
- package/dist/runtime/workspace-inventory.d.ts +51 -4
- package/dist/runtime/workspace-inventory.js +157 -4
- package/package.json +12 -2
- package/templates/_shared/base/{{slugKebabCase}}.php.mustache +3 -3
- package/templates/_shared/compound/core/{{slugKebabCase}}.php.mustache +3 -3
- package/templates/_shared/compound/persistence-auth/{{slugKebabCase}}.php.mustache +3 -3
- package/templates/_shared/compound/persistence-public/{{slugKebabCase}}.php.mustache +3 -3
- package/templates/_shared/persistence/auth/{{slugKebabCase}}.php.mustache +3 -3
- package/templates/_shared/persistence/core/{{slugKebabCase}}.php.mustache +3 -3
- package/templates/_shared/persistence/public/{{slugKebabCase}}.php.mustache +3 -3
- package/templates/query-loop/{{slugKebabCase}}.php.mustache +3 -3
|
@@ -11,6 +11,10 @@ const WORKSPACE_BINDING_SERVER_GLOB = "/src/bindings/*/server.php";
|
|
|
11
11
|
const WORKSPACE_BINDING_EDITOR_SCRIPT = "build/bindings/index.js";
|
|
12
12
|
const WORKSPACE_BINDING_EDITOR_ASSET = "build/bindings/index.asset.php";
|
|
13
13
|
const WORKSPACE_REST_RESOURCE_GLOB = "/inc/rest/*.php";
|
|
14
|
+
const WORKSPACE_ABILITY_GLOB = "/inc/abilities/*.php";
|
|
15
|
+
const WORKSPACE_ABILITY_EDITOR_SCRIPT = "build/abilities/index.js";
|
|
16
|
+
const WORKSPACE_ABILITY_EDITOR_ASSET = "build/abilities/index.asset.php";
|
|
17
|
+
const WORKSPACE_AI_FEATURE_GLOB = "/inc/ai-features/*.php";
|
|
14
18
|
const WORKSPACE_EDITOR_PLUGIN_EDITOR_SCRIPT = "build/editor-plugins/index.js";
|
|
15
19
|
const WORKSPACE_EDITOR_PLUGIN_EDITOR_ASSET = "build/editor-plugins/index.asset.php";
|
|
16
20
|
const WORKSPACE_EDITOR_PLUGIN_EDITOR_STYLE = "build/editor-plugins/style-index.css";
|
|
@@ -249,6 +253,126 @@ function checkWorkspaceRestResourceBootstrap(projectDir, packageName, phpPrefix)
|
|
|
249
253
|
? "REST resource PHP loader hook is present"
|
|
250
254
|
: "Missing REST resource PHP require glob or init hook");
|
|
251
255
|
}
|
|
256
|
+
function getWorkspaceAbilityRequiredFiles(ability) {
|
|
257
|
+
return Array.from(new Set([
|
|
258
|
+
ability.clientFile,
|
|
259
|
+
ability.configFile,
|
|
260
|
+
ability.dataFile,
|
|
261
|
+
ability.inputSchemaFile,
|
|
262
|
+
ability.outputSchemaFile,
|
|
263
|
+
ability.phpFile,
|
|
264
|
+
ability.typesFile,
|
|
265
|
+
]));
|
|
266
|
+
}
|
|
267
|
+
function checkWorkspaceAbilityConfig(projectDir, ability) {
|
|
268
|
+
const configPath = path.join(projectDir, ability.configFile);
|
|
269
|
+
if (!fs.existsSync(configPath)) {
|
|
270
|
+
return createDoctorCheck(`Ability config ${ability.slug}`, "fail", `Missing ${ability.configFile}`);
|
|
271
|
+
}
|
|
272
|
+
try {
|
|
273
|
+
const config = JSON.parse(fs.readFileSync(configPath, "utf8"));
|
|
274
|
+
const abilityId = typeof config.abilityId === "string" ? config.abilityId.trim() : "";
|
|
275
|
+
const categorySlug = typeof config.category?.slug === "string"
|
|
276
|
+
? config.category.slug.trim()
|
|
277
|
+
: "";
|
|
278
|
+
const hasValidAbilityId = /^[a-z0-9-]+\/[a-z0-9-]+$/u.test(abilityId);
|
|
279
|
+
const hasValidCategorySlug = /^[a-z0-9-]+$/u.test(categorySlug);
|
|
280
|
+
return createDoctorCheck(`Ability config ${ability.slug}`, hasValidAbilityId && hasValidCategorySlug ? "pass" : "fail", hasValidAbilityId && hasValidCategorySlug
|
|
281
|
+
? `Ability id ${abilityId} in category ${categorySlug} is valid`
|
|
282
|
+
: "Ability config must define a valid abilityId (`namespace/ability-name`) and category.slug.");
|
|
283
|
+
}
|
|
284
|
+
catch (error) {
|
|
285
|
+
return createDoctorCheck(`Ability config ${ability.slug}`, "fail", error instanceof Error ? error.message : String(error));
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
function checkWorkspaceAbilityBootstrap(projectDir, packageName, phpPrefix) {
|
|
289
|
+
const packageBaseName = packageName.split("/").pop() ?? packageName;
|
|
290
|
+
const bootstrapPath = path.join(projectDir, `${packageBaseName}.php`);
|
|
291
|
+
if (!fs.existsSync(bootstrapPath)) {
|
|
292
|
+
return createDoctorCheck("Ability bootstrap", "fail", `Missing ${path.basename(bootstrapPath)}`);
|
|
293
|
+
}
|
|
294
|
+
const source = fs.readFileSync(bootstrapPath, "utf8");
|
|
295
|
+
const loadFunctionName = `${phpPrefix}_load_workflow_abilities`;
|
|
296
|
+
const enqueueFunctionName = `${phpPrefix}_enqueue_workflow_abilities`;
|
|
297
|
+
const loadHook = `add_action( 'plugins_loaded', '${loadFunctionName}' );`;
|
|
298
|
+
const adminEnqueueHook = `add_action( 'admin_enqueue_scripts', '${enqueueFunctionName}' );`;
|
|
299
|
+
const editorEnqueueHook = `add_action( 'enqueue_block_editor_assets', '${enqueueFunctionName}' );`;
|
|
300
|
+
const hasLoaderHook = source.includes(loadHook);
|
|
301
|
+
const hasAdminEnqueueHook = source.includes(adminEnqueueHook);
|
|
302
|
+
const hasEditorEnqueueHook = source.includes(editorEnqueueHook);
|
|
303
|
+
const hasServerGlob = source.includes(WORKSPACE_ABILITY_GLOB);
|
|
304
|
+
const hasEditorScript = source.includes(WORKSPACE_ABILITY_EDITOR_SCRIPT);
|
|
305
|
+
const hasEditorAsset = source.includes(WORKSPACE_ABILITY_EDITOR_ASSET);
|
|
306
|
+
return createDoctorCheck("Ability bootstrap", hasLoaderHook &&
|
|
307
|
+
hasAdminEnqueueHook &&
|
|
308
|
+
hasEditorEnqueueHook &&
|
|
309
|
+
hasServerGlob &&
|
|
310
|
+
hasEditorScript &&
|
|
311
|
+
hasEditorAsset
|
|
312
|
+
? "pass"
|
|
313
|
+
: "fail", hasLoaderHook &&
|
|
314
|
+
hasAdminEnqueueHook &&
|
|
315
|
+
hasEditorEnqueueHook &&
|
|
316
|
+
hasServerGlob &&
|
|
317
|
+
hasEditorScript &&
|
|
318
|
+
hasEditorAsset
|
|
319
|
+
? "Ability loader and admin/editor client bootstrap hooks are present"
|
|
320
|
+
: "Missing ability loader hook or build/abilities script asset references");
|
|
321
|
+
}
|
|
322
|
+
function checkWorkspaceAbilityIndex(projectDir, abilities) {
|
|
323
|
+
const indexRelativePath = [
|
|
324
|
+
path.join("src", "abilities", "index.ts"),
|
|
325
|
+
path.join("src", "abilities", "index.js"),
|
|
326
|
+
].find((relativePath) => fs.existsSync(path.join(projectDir, relativePath)));
|
|
327
|
+
if (!indexRelativePath) {
|
|
328
|
+
return createDoctorCheck("Abilities index", "fail", "Missing src/abilities/index.ts or src/abilities/index.js");
|
|
329
|
+
}
|
|
330
|
+
const indexPath = path.join(projectDir, indexRelativePath);
|
|
331
|
+
const source = fs.readFileSync(indexPath, "utf8");
|
|
332
|
+
const missingExports = abilities.filter((ability) => {
|
|
333
|
+
const exportPattern = new RegExp(`^\\s*export\\s+(?:\\*\\s+from|\\{[^}]+\\}\\s+from)\\s+['"\`]\\./${escapeRegex(ability.slug)}\\/client['"\`]`, "mu");
|
|
334
|
+
return !exportPattern.test(source);
|
|
335
|
+
});
|
|
336
|
+
return createDoctorCheck("Abilities index", missingExports.length === 0 ? "pass" : "fail", missingExports.length === 0
|
|
337
|
+
? "Ability client helpers are aggregated"
|
|
338
|
+
: `Missing ability exports for: ${missingExports.map((entry) => entry.slug).join(", ")}`);
|
|
339
|
+
}
|
|
340
|
+
function getWorkspaceAiFeatureRequiredFiles(aiFeature) {
|
|
341
|
+
return Array.from(new Set([
|
|
342
|
+
aiFeature.aiSchemaFile,
|
|
343
|
+
aiFeature.apiFile,
|
|
344
|
+
path.join(path.dirname(aiFeature.typesFile), "api-schemas", "feature-request.schema.json"),
|
|
345
|
+
path.join(path.dirname(aiFeature.typesFile), "api-schemas", "feature-response.schema.json"),
|
|
346
|
+
path.join(path.dirname(aiFeature.typesFile), "api-schemas", "feature-result.schema.json"),
|
|
347
|
+
aiFeature.clientFile,
|
|
348
|
+
aiFeature.dataFile,
|
|
349
|
+
aiFeature.openApiFile,
|
|
350
|
+
aiFeature.phpFile,
|
|
351
|
+
aiFeature.typesFile,
|
|
352
|
+
aiFeature.validatorsFile,
|
|
353
|
+
]));
|
|
354
|
+
}
|
|
355
|
+
function checkWorkspaceAiFeatureConfig(aiFeature) {
|
|
356
|
+
const hasNamespace = REST_RESOURCE_NAMESPACE_PATTERN.test(aiFeature.namespace);
|
|
357
|
+
return createDoctorCheck(`AI feature config ${aiFeature.slug}`, hasNamespace ? "pass" : "fail", hasNamespace
|
|
358
|
+
? `AI feature namespace ${aiFeature.namespace} is valid`
|
|
359
|
+
: "AI feature namespace is invalid");
|
|
360
|
+
}
|
|
361
|
+
function checkWorkspaceAiFeatureBootstrap(projectDir, packageName, phpPrefix) {
|
|
362
|
+
const packageBaseName = packageName.split("/").pop() ?? packageName;
|
|
363
|
+
const bootstrapPath = path.join(projectDir, `${packageBaseName}.php`);
|
|
364
|
+
if (!fs.existsSync(bootstrapPath)) {
|
|
365
|
+
return createDoctorCheck("AI feature bootstrap", "fail", `Missing ${path.basename(bootstrapPath)}`);
|
|
366
|
+
}
|
|
367
|
+
const source = fs.readFileSync(bootstrapPath, "utf8");
|
|
368
|
+
const registerFunctionName = `${phpPrefix}_register_ai_features`;
|
|
369
|
+
const registerHook = `add_action( 'init', '${registerFunctionName}', 20 );`;
|
|
370
|
+
const hasServerGlob = source.includes(WORKSPACE_AI_FEATURE_GLOB);
|
|
371
|
+
const hasRegisterHook = source.includes(registerHook);
|
|
372
|
+
return createDoctorCheck("AI feature bootstrap", hasServerGlob && hasRegisterHook ? "pass" : "fail", hasServerGlob && hasRegisterHook
|
|
373
|
+
? "AI feature PHP loader hook is present"
|
|
374
|
+
: "Missing AI feature PHP require glob or init hook");
|
|
375
|
+
}
|
|
252
376
|
function getWorkspaceEditorPluginRequiredFiles(editorPlugin) {
|
|
253
377
|
const editorPluginDir = path.join("src", "editor-plugins", editorPlugin.slug);
|
|
254
378
|
return Array.from(new Set([
|
|
@@ -377,7 +501,7 @@ export function getWorkspaceDoctorChecks(cwd) {
|
|
|
377
501
|
checks.push(checkWorkspacePackageMetadata(workspace, workspacePackageJson));
|
|
378
502
|
try {
|
|
379
503
|
const inventory = readWorkspaceInventory(workspace.projectDir);
|
|
380
|
-
checks.push(createDoctorCheck("Workspace inventory", "pass", `${inventory.blocks.length} block(s), ${inventory.variations.length} variation(s), ${inventory.patterns.length} pattern(s), ${inventory.bindingSources.length} binding source(s), ${inventory.restResources.length} REST resource(s), ${inventory.editorPlugins.length} editor plugin(s)`));
|
|
504
|
+
checks.push(createDoctorCheck("Workspace inventory", "pass", `${inventory.blocks.length} block(s), ${inventory.variations.length} variation(s), ${inventory.patterns.length} pattern(s), ${inventory.bindingSources.length} binding source(s), ${inventory.restResources.length} REST resource(s), ${inventory.abilities.length} ability scaffold(s), ${inventory.aiFeatures.length} AI feature(s), ${inventory.editorPlugins.length} editor plugin(s)`));
|
|
381
505
|
for (const block of inventory.blocks) {
|
|
382
506
|
checks.push(checkExistingFiles(workspace.projectDir, `Block ${block.slug}`, getWorkspaceBlockRequiredFiles(block)));
|
|
383
507
|
checks.push(checkWorkspaceBlockMetadata(workspace.projectDir, workspace, block));
|
|
@@ -422,6 +546,21 @@ export function getWorkspaceDoctorChecks(cwd) {
|
|
|
422
546
|
checks.push(checkWorkspaceRestResourceConfig(restResource));
|
|
423
547
|
checks.push(checkExistingFiles(workspace.projectDir, `REST resource ${restResource.slug}`, getWorkspaceRestResourceRequiredFiles(restResource)));
|
|
424
548
|
}
|
|
549
|
+
if (inventory.abilities.length > 0) {
|
|
550
|
+
checks.push(checkWorkspaceAbilityBootstrap(workspace.projectDir, workspace.packageName, workspace.workspace.phpPrefix));
|
|
551
|
+
checks.push(checkWorkspaceAbilityIndex(workspace.projectDir, inventory.abilities));
|
|
552
|
+
}
|
|
553
|
+
for (const ability of inventory.abilities) {
|
|
554
|
+
checks.push(checkWorkspaceAbilityConfig(workspace.projectDir, ability));
|
|
555
|
+
checks.push(checkExistingFiles(workspace.projectDir, `Ability ${ability.slug}`, getWorkspaceAbilityRequiredFiles(ability)));
|
|
556
|
+
}
|
|
557
|
+
if (inventory.aiFeatures.length > 0) {
|
|
558
|
+
checks.push(checkWorkspaceAiFeatureBootstrap(workspace.projectDir, workspace.packageName, workspace.workspace.phpPrefix));
|
|
559
|
+
}
|
|
560
|
+
for (const aiFeature of inventory.aiFeatures) {
|
|
561
|
+
checks.push(checkWorkspaceAiFeatureConfig(aiFeature));
|
|
562
|
+
checks.push(checkExistingFiles(workspace.projectDir, `AI feature ${aiFeature.slug}`, getWorkspaceAiFeatureRequiredFiles(aiFeature)));
|
|
563
|
+
}
|
|
425
564
|
if (inventory.editorPlugins.length > 0) {
|
|
426
565
|
checks.push(checkWorkspaceEditorPluginBootstrap(workspace.projectDir, workspace.packageName, workspace.workspace.phpPrefix));
|
|
427
566
|
checks.push(checkWorkspaceEditorPluginIndex(workspace.projectDir, inventory.editorPlugins));
|
package/dist/runtime/cli-help.js
CHANGED
|
@@ -23,6 +23,8 @@ export function formatHelpText() {
|
|
|
23
23
|
wp-typia add pattern <name>
|
|
24
24
|
wp-typia add binding-source <name>
|
|
25
25
|
wp-typia add rest-resource <name> [--namespace <vendor/v1>] [--methods <method[,method...]>]
|
|
26
|
+
wp-typia add ability <name>
|
|
27
|
+
wp-typia add ai-feature <name> [--namespace <vendor/v1>]
|
|
26
28
|
wp-typia add editor-plugin <name> [--slot <PluginSidebar>]
|
|
27
29
|
wp-typia add hooked-block <block-slug> --anchor <anchor-block-name> --position <before|after|firstChild|lastChild>
|
|
28
30
|
wp-typia migrate <init|snapshot|diff|scaffold|verify|doctor|fixtures|fuzz> [...]
|
|
@@ -45,6 +47,8 @@ Notes:
|
|
|
45
47
|
\`add pattern\` scaffolds a namespaced PHP pattern shell under \`src/patterns/\`.
|
|
46
48
|
\`add binding-source\` scaffolds shared PHP and editor registration under \`src/bindings/\`.
|
|
47
49
|
\`add rest-resource\` scaffolds plugin-level TypeScript REST contracts under \`src/rest/\` and PHP route glue under \`inc/rest/\`.
|
|
50
|
+
\`add ability\` scaffolds typed workflow abilities under \`src/abilities/\` and server registration under \`inc/abilities/\`.
|
|
51
|
+
\`add ai-feature\` scaffolds server-owned AI feature endpoints under \`src/ai-features/\` and PHP route glue under \`inc/ai-features/\`.
|
|
48
52
|
\`add editor-plugin\` scaffolds a document-level editor extension under \`src/editor-plugins/\`.
|
|
49
53
|
\`add hooked-block\` patches an existing workspace block's \`block.json\` \`blockHooks\` metadata.
|
|
50
54
|
\`wp-typia doctor\` always checks environment readiness and reports when it only ran environment-level diagnostics; official workspace roots also get inventory and source-tree drift checks.
|
package/dist/runtime/index.d.ts
CHANGED
|
@@ -13,6 +13,8 @@
|
|
|
13
13
|
export { COMPOUND_INNER_BLOCKS_PRESET_IDS, DEFAULT_COMPOUND_INNER_BLOCKS_PRESET_ID, getCompoundInnerBlocksPresetDefinition, isCompoundInnerBlocksPresetId, parseCompoundInnerBlocksPreset, resolveCompoundInnerBlocksPreset, } from "./compound-inner-blocks.js";
|
|
14
14
|
export type { CompoundInnerBlocksPresetDefinition, CompoundInnerBlocksPresetId, CompoundInnerBlocksTemplateLock, } from "./compound-inner-blocks.js";
|
|
15
15
|
export { scaffoldProject, collectScaffoldAnswers, getScaffoldTemplateVariableGroups, getDefaultAnswers, getTemplateVariables, resolvePackageManagerId, resolveTemplateId, } from "./scaffold.js";
|
|
16
|
+
export { DEFAULT_SCAFFOLD_COMPATIBILITY, OPTIONAL_WORDPRESS_AI_CLIENT_COMPATIBILITY, REQUIRED_WORKSPACE_ABILITY_COMPATIBILITY, createScaffoldCompatibilityConfig, renderScaffoldCompatibilityConfig, resolveScaffoldCompatibilityPolicy, updatePluginHeaderCompatibility, } from "./scaffold-compatibility.js";
|
|
17
|
+
export type { ScaffoldCompatibilityConfig, ScaffoldCompatibilityPolicy, ScaffoldPluginHeaderCompatibility, } from "./scaffold-compatibility.js";
|
|
16
18
|
export { BlockGeneratorService } from "./block-generator-service.js";
|
|
17
19
|
export type { BasicScaffoldTemplateVariableGroups, CompoundScaffoldTemplateVariableGroups, ExternalScaffoldTemplateVariableGroups, FlatScaffoldTemplateVariables, InteractivityScaffoldTemplateVariableGroups, PersistenceScaffoldTemplateVariableGroups, QueryLoopScaffoldTemplateVariableGroups, ScaffoldTemplateFamily, ScaffoldTemplateVariableGroups, } from "./scaffold.js";
|
|
18
20
|
export type { ApplyBlockInput, BlockGenerationTarget, BlockSpec, PlanBlockInput, PlanBlockResult, RenderBlockInput, RenderBlockResult, ValidateBlockInput, ValidateBlockResult, } from "./block-generator-service.js";
|
|
@@ -26,5 +28,5 @@ export type { EndpointAuthIntent, EndpointOpenApiAuthMode, EndpointOpenApiContra
|
|
|
26
28
|
export { PACKAGE_MANAGER_IDS, PACKAGE_MANAGERS, formatPackageExecCommand, formatInstallCommand, formatRunScript, getPackageManager, getPackageManagerSelectOptions, transformPackageManagerText, } from "./package-managers.js";
|
|
27
29
|
export { TEMPLATE_IDS, TEMPLATE_REGISTRY, getTemplateById, getTemplateSelectOptions, listTemplates, } from "./template-registry.js";
|
|
28
30
|
export { STALE_TEMP_ROOT_MAX_AGE_MS, WP_TYPIA_TEMP_ROOT_PREFIX, cleanupManagedTempRoot, cleanupStaleTempRoots, createManagedTempRoot, getTrackedTempRoots, } from "./temp-roots.js";
|
|
29
|
-
export { createReadlinePrompt, createCliCommandError, CliDiagnosticError, formatCliDiagnosticError, formatAddHelpText, formatDoctorCheckLine, formatDoctorSummaryLine, formatHelpText, formatTemplateDetails, formatTemplateFeatures, formatTemplateSummary, getDoctorChecks, getDoctorFailureDetailLines, getFailingDoctorChecks, getNextSteps, getOptionalOnboarding, getWorkspaceBlockSelectOptions, HOOKED_BLOCK_POSITION_IDS, EDITOR_PLUGIN_SLOT_IDS, isCliDiagnosticError, runAddBindingSourceCommand, runAddBlockCommand, runAddEditorPluginCommand, runAddHookedBlockCommand, runAddPatternCommand, runDoctor, runAddVariationCommand, runScaffoldFlow, } from "./cli-core.js";
|
|
31
|
+
export { createReadlinePrompt, createCliCommandError, CliDiagnosticError, formatCliDiagnosticError, formatAddHelpText, formatDoctorCheckLine, formatDoctorSummaryLine, formatHelpText, formatTemplateDetails, formatTemplateFeatures, formatTemplateSummary, getDoctorChecks, getDoctorFailureDetailLines, getFailingDoctorChecks, getNextSteps, getOptionalOnboarding, getWorkspaceBlockSelectOptions, HOOKED_BLOCK_POSITION_IDS, EDITOR_PLUGIN_SLOT_IDS, isCliDiagnosticError, runAddAbilityCommand, runAddAiFeatureCommand, runAddBindingSourceCommand, runAddBlockCommand, runAddEditorPluginCommand, runAddHookedBlockCommand, runAddPatternCommand, runDoctor, runAddVariationCommand, runScaffoldFlow, } from "./cli-core.js";
|
|
30
32
|
export type { CliDiagnosticMessage, DoctorCheck, EditorPluginSlotId, HookedBlockPositionId, ReadlinePrompt, } from "./cli-core.js";
|
package/dist/runtime/index.js
CHANGED
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
*/
|
|
13
13
|
export { COMPOUND_INNER_BLOCKS_PRESET_IDS, DEFAULT_COMPOUND_INNER_BLOCKS_PRESET_ID, getCompoundInnerBlocksPresetDefinition, isCompoundInnerBlocksPresetId, parseCompoundInnerBlocksPreset, resolveCompoundInnerBlocksPreset, } from "./compound-inner-blocks.js";
|
|
14
14
|
export { scaffoldProject, collectScaffoldAnswers, getScaffoldTemplateVariableGroups, getDefaultAnswers, getTemplateVariables, resolvePackageManagerId, resolveTemplateId, } from "./scaffold.js";
|
|
15
|
+
export { DEFAULT_SCAFFOLD_COMPATIBILITY, OPTIONAL_WORDPRESS_AI_CLIENT_COMPATIBILITY, REQUIRED_WORKSPACE_ABILITY_COMPATIBILITY, createScaffoldCompatibilityConfig, renderScaffoldCompatibilityConfig, resolveScaffoldCompatibilityPolicy, updatePluginHeaderCompatibility, } from "./scaffold-compatibility.js";
|
|
15
16
|
export { BlockGeneratorService } from "./block-generator-service.js";
|
|
16
17
|
export { BLOCK_GENERATION_TOOL_CONTRACT_VERSION, inspectBlockGeneration, } from "./block-generator-tool-contract.js";
|
|
17
18
|
export { formatMigrationHelpText, parseMigrationArgs, runMigrationCommand, } from "./migrations.js";
|
|
@@ -21,4 +22,4 @@ export { buildCompoundChildStarterManifestDocument, getStarterManifestFiles, str
|
|
|
21
22
|
export { PACKAGE_MANAGER_IDS, PACKAGE_MANAGERS, formatPackageExecCommand, formatInstallCommand, formatRunScript, getPackageManager, getPackageManagerSelectOptions, transformPackageManagerText, } from "./package-managers.js";
|
|
22
23
|
export { TEMPLATE_IDS, TEMPLATE_REGISTRY, getTemplateById, getTemplateSelectOptions, listTemplates, } from "./template-registry.js";
|
|
23
24
|
export { STALE_TEMP_ROOT_MAX_AGE_MS, WP_TYPIA_TEMP_ROOT_PREFIX, cleanupManagedTempRoot, cleanupStaleTempRoots, createManagedTempRoot, getTrackedTempRoots, } from "./temp-roots.js";
|
|
24
|
-
export { createReadlinePrompt, createCliCommandError, CliDiagnosticError, formatCliDiagnosticError, formatAddHelpText, formatDoctorCheckLine, formatDoctorSummaryLine, formatHelpText, formatTemplateDetails, formatTemplateFeatures, formatTemplateSummary, getDoctorChecks, getDoctorFailureDetailLines, getFailingDoctorChecks, getNextSteps, getOptionalOnboarding, getWorkspaceBlockSelectOptions, HOOKED_BLOCK_POSITION_IDS, EDITOR_PLUGIN_SLOT_IDS, isCliDiagnosticError, runAddBindingSourceCommand, runAddBlockCommand, runAddEditorPluginCommand, runAddHookedBlockCommand, runAddPatternCommand, runDoctor, runAddVariationCommand, runScaffoldFlow, } from "./cli-core.js";
|
|
25
|
+
export { createReadlinePrompt, createCliCommandError, CliDiagnosticError, formatCliDiagnosticError, formatAddHelpText, formatDoctorCheckLine, formatDoctorSummaryLine, formatHelpText, formatTemplateDetails, formatTemplateFeatures, formatTemplateSummary, getDoctorChecks, getDoctorFailureDetailLines, getFailingDoctorChecks, getNextSteps, getOptionalOnboarding, getWorkspaceBlockSelectOptions, HOOKED_BLOCK_POSITION_IDS, EDITOR_PLUGIN_SLOT_IDS, isCliDiagnosticError, runAddAbilityCommand, runAddAiFeatureCommand, runAddBindingSourceCommand, runAddBlockCommand, runAddEditorPluginCommand, runAddHookedBlockCommand, runAddPatternCommand, runDoctor, runAddVariationCommand, runScaffoldFlow, } from "./cli-core.js";
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Compatibility policy helpers for generated scaffold outputs.
|
|
3
|
+
*
|
|
4
|
+
* The policy keeps plugin headers, runtime gates, and workspace inventory
|
|
5
|
+
* metadata aligned when optional or required AI-capable features are added.
|
|
6
|
+
*/
|
|
7
|
+
import { type AiFeatureCapabilitySelection, type AiFeatureCompatibilityFloor, type ResolvedAiFeatureCapabilityPlan } from "./ai-feature-capability.js";
|
|
8
|
+
/**
|
|
9
|
+
* WordPress plugin header version floors emitted by scaffold templates.
|
|
10
|
+
*/
|
|
11
|
+
export interface ScaffoldPluginHeaderCompatibility {
|
|
12
|
+
requiresAtLeast: string;
|
|
13
|
+
requiresPhp: string;
|
|
14
|
+
testedUpTo: string;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Resolved compatibility policy for a set of scaffold feature capabilities.
|
|
18
|
+
*/
|
|
19
|
+
export interface ScaffoldCompatibilityPolicy {
|
|
20
|
+
capabilityPlan: ResolvedAiFeatureCapabilityPlan;
|
|
21
|
+
pluginHeader: ScaffoldPluginHeaderCompatibility;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Serializable compatibility metadata stored in generated workspace inventory.
|
|
25
|
+
*/
|
|
26
|
+
export interface ScaffoldCompatibilityConfig {
|
|
27
|
+
hardMinimums: AiFeatureCompatibilityFloor;
|
|
28
|
+
mode: "baseline" | "optional" | "required";
|
|
29
|
+
optionalFeatures: string[];
|
|
30
|
+
requiredFeatures: string[];
|
|
31
|
+
runtimeGates: string[];
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Baseline headers used by scaffold output before optional features are added.
|
|
35
|
+
*/
|
|
36
|
+
export declare const DEFAULT_SCAFFOLD_COMPATIBILITY: ScaffoldPluginHeaderCompatibility;
|
|
37
|
+
/**
|
|
38
|
+
* Optional WordPress AI Client surface used by server-only AI feature scaffold.
|
|
39
|
+
*/
|
|
40
|
+
export declare const OPTIONAL_WORDPRESS_AI_CLIENT_COMPATIBILITY: readonly AiFeatureCapabilitySelection[];
|
|
41
|
+
/**
|
|
42
|
+
* Required Abilities API surface used by typed workflow ability scaffold.
|
|
43
|
+
*/
|
|
44
|
+
export declare const REQUIRED_WORKSPACE_ABILITY_COMPATIBILITY: readonly AiFeatureCapabilitySelection[];
|
|
45
|
+
/**
|
|
46
|
+
* Resolve plugin header floors and capability gates for scaffold selections.
|
|
47
|
+
*/
|
|
48
|
+
export declare function resolveScaffoldCompatibilityPolicy(selections: readonly AiFeatureCapabilitySelection[], { baseline, }?: {
|
|
49
|
+
baseline?: ScaffoldPluginHeaderCompatibility;
|
|
50
|
+
}): ScaffoldCompatibilityPolicy;
|
|
51
|
+
/**
|
|
52
|
+
* Convert a resolved policy into workspace-inventory-safe JSON metadata.
|
|
53
|
+
*/
|
|
54
|
+
export declare function createScaffoldCompatibilityConfig(policy: ScaffoldCompatibilityPolicy): ScaffoldCompatibilityConfig;
|
|
55
|
+
/**
|
|
56
|
+
* Render compatibility metadata as formatted TypeScript object literal JSON.
|
|
57
|
+
*/
|
|
58
|
+
export declare function renderScaffoldCompatibilityConfig(policy: ScaffoldCompatibilityPolicy, indent?: string): string;
|
|
59
|
+
/**
|
|
60
|
+
* Patch a generated plugin bootstrap header without lowering custom floors.
|
|
61
|
+
*
|
|
62
|
+
* Preserves the original header line endings while replacing empty or invalid
|
|
63
|
+
* version strings with the policy values.
|
|
64
|
+
*/
|
|
65
|
+
export declare function updatePluginHeaderCompatibility(source: string, policy: ScaffoldCompatibilityPolicy): string;
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Compatibility policy helpers for generated scaffold outputs.
|
|
3
|
+
*
|
|
4
|
+
* The policy keeps plugin headers, runtime gates, and workspace inventory
|
|
5
|
+
* metadata aligned when optional or required AI-capable features are added.
|
|
6
|
+
*/
|
|
7
|
+
import { AI_FEATURE_DEFINITIONS, resolveAiFeatureCapabilityPlan, } from "./ai-feature-capability.js";
|
|
8
|
+
/**
|
|
9
|
+
* Baseline headers used by scaffold output before optional features are added.
|
|
10
|
+
*/
|
|
11
|
+
export const DEFAULT_SCAFFOLD_COMPATIBILITY = {
|
|
12
|
+
requiresAtLeast: "6.7",
|
|
13
|
+
requiresPhp: "8.0",
|
|
14
|
+
testedUpTo: "6.9",
|
|
15
|
+
};
|
|
16
|
+
/**
|
|
17
|
+
* Optional WordPress AI Client surface used by server-only AI feature scaffold.
|
|
18
|
+
*/
|
|
19
|
+
export const OPTIONAL_WORDPRESS_AI_CLIENT_COMPATIBILITY = [
|
|
20
|
+
{
|
|
21
|
+
featureId: AI_FEATURE_DEFINITIONS.wordpressAiClient.id,
|
|
22
|
+
mode: "optional",
|
|
23
|
+
},
|
|
24
|
+
];
|
|
25
|
+
/**
|
|
26
|
+
* Required Abilities API surface used by typed workflow ability scaffold.
|
|
27
|
+
*/
|
|
28
|
+
export const REQUIRED_WORKSPACE_ABILITY_COMPATIBILITY = [
|
|
29
|
+
{
|
|
30
|
+
featureId: AI_FEATURE_DEFINITIONS.wordpressServerAbilities.id,
|
|
31
|
+
mode: "required",
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
featureId: AI_FEATURE_DEFINITIONS.wordpressCoreAbilities.id,
|
|
35
|
+
mode: "required",
|
|
36
|
+
},
|
|
37
|
+
];
|
|
38
|
+
function parseVersionFloorParts(value) {
|
|
39
|
+
return value.split(".").map((part, index) => {
|
|
40
|
+
if (!/^\d+$/u.test(part)) {
|
|
41
|
+
throw new Error(`parseVersionFloorParts received an invalid version floor "${value}" at segment ${index + 1}.`);
|
|
42
|
+
}
|
|
43
|
+
return Number.parseInt(part, 10);
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
function compareVersionFloors(left, right) {
|
|
47
|
+
const leftParts = parseVersionFloorParts(left);
|
|
48
|
+
const rightParts = parseVersionFloorParts(right);
|
|
49
|
+
const length = Math.max(leftParts.length, rightParts.length);
|
|
50
|
+
for (let index = 0; index < length; index += 1) {
|
|
51
|
+
const leftValue = leftParts[index] ?? 0;
|
|
52
|
+
const rightValue = rightParts[index] ?? 0;
|
|
53
|
+
if (leftValue > rightValue) {
|
|
54
|
+
return 1;
|
|
55
|
+
}
|
|
56
|
+
if (leftValue < rightValue) {
|
|
57
|
+
return -1;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
return 0;
|
|
61
|
+
}
|
|
62
|
+
function pickHigherVersionFloor(current, candidate) {
|
|
63
|
+
if (!candidate) {
|
|
64
|
+
return current;
|
|
65
|
+
}
|
|
66
|
+
return compareVersionFloors(current, candidate) >= 0 ? current : candidate;
|
|
67
|
+
}
|
|
68
|
+
function pickHigherHeaderVersionFloor(policyValue, currentValue) {
|
|
69
|
+
const normalizedCurrentValue = currentValue.trim();
|
|
70
|
+
if (!normalizedCurrentValue) {
|
|
71
|
+
return policyValue;
|
|
72
|
+
}
|
|
73
|
+
try {
|
|
74
|
+
return pickHigherVersionFloor(policyValue, normalizedCurrentValue);
|
|
75
|
+
}
|
|
76
|
+
catch {
|
|
77
|
+
return policyValue;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
function formatRuntimeGate(feature) {
|
|
81
|
+
return (feature.runtimeGates ?? []).map((gate) => `${feature.label}: ${gate.kind} ${gate.value}`);
|
|
82
|
+
}
|
|
83
|
+
function getPolicyMode(capabilityPlan) {
|
|
84
|
+
if (capabilityPlan.requiredFeatures.length > 0) {
|
|
85
|
+
return "required";
|
|
86
|
+
}
|
|
87
|
+
if (capabilityPlan.optionalFeatures.length > 0) {
|
|
88
|
+
return "optional";
|
|
89
|
+
}
|
|
90
|
+
return "baseline";
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Resolve plugin header floors and capability gates for scaffold selections.
|
|
94
|
+
*/
|
|
95
|
+
export function resolveScaffoldCompatibilityPolicy(selections, { baseline = DEFAULT_SCAFFOLD_COMPATIBILITY, } = {}) {
|
|
96
|
+
const capabilityPlan = resolveAiFeatureCapabilityPlan(selections);
|
|
97
|
+
const requiresAtLeast = pickHigherVersionFloor(baseline.requiresAtLeast, capabilityPlan.hardMinimums.wordpress);
|
|
98
|
+
const requiresPhp = pickHigherVersionFloor(baseline.requiresPhp, capabilityPlan.hardMinimums.php);
|
|
99
|
+
const testedUpTo = pickHigherVersionFloor(baseline.testedUpTo, requiresAtLeast);
|
|
100
|
+
return {
|
|
101
|
+
capabilityPlan,
|
|
102
|
+
pluginHeader: {
|
|
103
|
+
requiresAtLeast,
|
|
104
|
+
requiresPhp,
|
|
105
|
+
testedUpTo,
|
|
106
|
+
},
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Convert a resolved policy into workspace-inventory-safe JSON metadata.
|
|
111
|
+
*/
|
|
112
|
+
export function createScaffoldCompatibilityConfig(policy) {
|
|
113
|
+
const { capabilityPlan } = policy;
|
|
114
|
+
return {
|
|
115
|
+
hardMinimums: capabilityPlan.hardMinimums,
|
|
116
|
+
mode: getPolicyMode(capabilityPlan),
|
|
117
|
+
optionalFeatures: capabilityPlan.optionalFeatures.map((feature) => feature.label),
|
|
118
|
+
requiredFeatures: capabilityPlan.requiredFeatures.map((feature) => feature.label),
|
|
119
|
+
runtimeGates: [
|
|
120
|
+
...capabilityPlan.requiredFeatures.flatMap(formatRuntimeGate),
|
|
121
|
+
...capabilityPlan.optionalFeatures.flatMap(formatRuntimeGate),
|
|
122
|
+
],
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* Render compatibility metadata as formatted TypeScript object literal JSON.
|
|
127
|
+
*/
|
|
128
|
+
export function renderScaffoldCompatibilityConfig(policy, indent = "\t\t") {
|
|
129
|
+
const config = createScaffoldCompatibilityConfig(policy);
|
|
130
|
+
return JSON.stringify(config, null, "\t")
|
|
131
|
+
.split("\n")
|
|
132
|
+
.map((line, index) => (index === 0 ? line : `${indent}${line}`))
|
|
133
|
+
.join("\n");
|
|
134
|
+
}
|
|
135
|
+
function replacePluginHeaderVersionFloor(source, pattern, policyValue) {
|
|
136
|
+
return source.replace(pattern, (_match, prefix, currentValue, lineEnding) => {
|
|
137
|
+
const versionPrefix = prefix.endsWith(":") ? `${prefix} ` : prefix;
|
|
138
|
+
return `${versionPrefix}${pickHigherHeaderVersionFloor(policyValue, currentValue)}${lineEnding}`;
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* Patch a generated plugin bootstrap header without lowering custom floors.
|
|
143
|
+
*
|
|
144
|
+
* Preserves the original header line endings while replacing empty or invalid
|
|
145
|
+
* version strings with the policy values.
|
|
146
|
+
*/
|
|
147
|
+
export function updatePluginHeaderCompatibility(source, policy) {
|
|
148
|
+
const { pluginHeader } = policy;
|
|
149
|
+
const nextSource = replacePluginHeaderVersionFloor(source, /(\* Requires at least:[^\S\r\n]*)([^\r\n]*)(\r?)/u, pluginHeader.requiresAtLeast);
|
|
150
|
+
const nextSourceWithTestedUpTo = replacePluginHeaderVersionFloor(nextSource, /(\* Tested up to:[^\S\r\n]*)([^\r\n]*)(\r?)/u, pluginHeader.testedUpTo);
|
|
151
|
+
return replacePluginHeaderVersionFloor(nextSourceWithTestedUpTo, /(\* Requires PHP:[^\S\r\n]*)([^\r\n]*)(\r?)/u, pluginHeader.requiresPhp);
|
|
152
|
+
}
|
|
@@ -1,9 +1,11 @@
|
|
|
1
|
+
import type { ScaffoldPluginHeaderCompatibility } from "./scaffold-compatibility.js";
|
|
1
2
|
export declare const SCAFFOLD_TEMPLATE_VARIABLE_GROUPS: unique symbol;
|
|
2
3
|
export type ScaffoldTemplateFamily = "basic" | "interactivity" | "persistence" | "compound" | "query-loop" | "external";
|
|
3
4
|
export interface ScaffoldSharedTemplateVariableGroup {
|
|
4
5
|
author: string;
|
|
5
6
|
blockMetadataVersion: string;
|
|
6
7
|
category: string;
|
|
8
|
+
compatibility: ScaffoldPluginHeaderCompatibility;
|
|
7
9
|
cssClassName: string;
|
|
8
10
|
description: string;
|
|
9
11
|
descriptionJson: string;
|
|
@@ -6,6 +6,7 @@ import { DEFAULT_COMPOUND_INNER_BLOCKS_PRESET_ID, getCompoundInnerBlocksPresetDe
|
|
|
6
6
|
import { getTemplateById, isBuiltInTemplateId, } from './template-registry.js';
|
|
7
7
|
import { toPascalCase, toSnakeCase, } from './string-case.js';
|
|
8
8
|
import { attachScaffoldTemplateVariableGroups } from "./scaffold-template-variable-groups.js";
|
|
9
|
+
import { resolveScaffoldCompatibilityPolicy } from "./scaffold-compatibility.js";
|
|
9
10
|
/**
|
|
10
11
|
* Build the normalized template variables used by scaffold rendering.
|
|
11
12
|
*
|
|
@@ -58,6 +59,7 @@ export function getTemplateVariables(templateId, answers) {
|
|
|
58
59
|
const persistencePolicy = templateId === 'persistence' || compoundPersistenceEnabled
|
|
59
60
|
? answers.persistencePolicy ?? 'authenticated'
|
|
60
61
|
: 'authenticated';
|
|
62
|
+
const compatibility = resolveScaffoldCompatibilityPolicy([]);
|
|
61
63
|
const flatVariables = {
|
|
62
64
|
alternateRenderTargetsCsv: '',
|
|
63
65
|
alternateRenderTargetsJson: '[]',
|
|
@@ -92,6 +94,8 @@ export function getTemplateVariables(templateId, answers) {
|
|
|
92
94
|
hasAlternatePlainTextRenderTarget: 'false',
|
|
93
95
|
hasAlternateRenderTargets: 'false',
|
|
94
96
|
projectToolsPackageVersion,
|
|
97
|
+
requiresAtLeast: compatibility.pluginHeader.requiresAtLeast,
|
|
98
|
+
requiresPhp: compatibility.pluginHeader.requiresPhp,
|
|
95
99
|
cssClassName,
|
|
96
100
|
dataStorageMode,
|
|
97
101
|
dashCase: slug,
|
|
@@ -118,6 +122,7 @@ export function getTemplateVariables(templateId, answers) {
|
|
|
118
122
|
phpPrefix,
|
|
119
123
|
phpPrefixUpper,
|
|
120
124
|
restPackageVersion,
|
|
125
|
+
testedUpTo: compatibility.pluginHeader.testedUpTo,
|
|
121
126
|
publicWriteRequestIdDeclaration: persistencePolicy === 'public'
|
|
122
127
|
? "publicWriteRequestId: string & tags.MinLength< 1 > & tags.MaxLength< 128 >;"
|
|
123
128
|
: "publicWriteRequestId?: string & tags.MinLength< 1 > & tags.MaxLength< 128 >;",
|
|
@@ -162,6 +167,7 @@ export function getTemplateVariables(templateId, answers) {
|
|
|
162
167
|
author: answers.author.trim(),
|
|
163
168
|
blockMetadataVersion: BUILTIN_BLOCK_METADATA_VERSION,
|
|
164
169
|
category: metadataDefaults?.category ?? template?.defaultCategory ?? 'widgets',
|
|
170
|
+
compatibility: compatibility.pluginHeader,
|
|
165
171
|
cssClassName,
|
|
166
172
|
description,
|
|
167
173
|
descriptionJson: JSON.stringify(description),
|
|
@@ -58,6 +58,8 @@ export interface FlatScaffoldTemplateVariables extends Record<string, string> {
|
|
|
58
58
|
hasAlternatePlainTextRenderTarget: "false" | "true";
|
|
59
59
|
hasAlternateRenderTargets: "false" | "true";
|
|
60
60
|
projectToolsPackageVersion: string;
|
|
61
|
+
requiresAtLeast: string;
|
|
62
|
+
requiresPhp: string;
|
|
61
63
|
cssClassName: string;
|
|
62
64
|
dashCase: string;
|
|
63
65
|
dataStorageMode: DataStorageMode;
|
|
@@ -90,6 +92,7 @@ export interface FlatScaffoldTemplateVariables extends Record<string, string> {
|
|
|
90
92
|
slugSnakeCase: string;
|
|
91
93
|
textDomain: string;
|
|
92
94
|
textdomain: string;
|
|
95
|
+
testedUpTo: string;
|
|
93
96
|
title: string;
|
|
94
97
|
titleJson: string;
|
|
95
98
|
titleCase: string;
|