@wp-typia/project-tools 0.23.0 → 0.24.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/dist/runtime/ai-feature-artifacts.js +4 -1
- package/dist/runtime/block-generator-service-spec.js +2 -1
- package/dist/runtime/built-in-block-non-ts-basic-artifacts.d.ts +9 -0
- package/dist/runtime/built-in-block-non-ts-basic-artifacts.js +84 -0
- package/dist/runtime/built-in-block-non-ts-compound-artifacts.d.ts +9 -0
- package/dist/runtime/built-in-block-non-ts-compound-artifacts.js +36 -0
- package/dist/runtime/built-in-block-non-ts-compound-templates.d.ts +23 -0
- package/dist/runtime/built-in-block-non-ts-compound-templates.js +453 -0
- package/dist/runtime/built-in-block-non-ts-family-artifacts.d.ts +8 -26
- package/dist/runtime/built-in-block-non-ts-family-artifacts.js +8 -1034
- package/dist/runtime/built-in-block-non-ts-interactivity-artifacts.d.ts +9 -0
- package/dist/runtime/built-in-block-non-ts-interactivity-artifacts.js +83 -0
- package/dist/runtime/built-in-block-non-ts-persistence-artifacts.d.ts +9 -0
- package/dist/runtime/built-in-block-non-ts-persistence-artifacts.js +33 -0
- package/dist/runtime/built-in-block-non-ts-persistence-templates.d.ts +23 -0
- package/dist/runtime/built-in-block-non-ts-persistence-templates.js +395 -0
- package/dist/runtime/cli-add-block-json.js +5 -1
- package/dist/runtime/cli-add-collision.js +8 -0
- package/dist/runtime/cli-add-help.js +14 -10
- package/dist/runtime/cli-add-kind-ids.d.ts +1 -1
- package/dist/runtime/cli-add-kind-ids.js +1 -0
- package/dist/runtime/cli-add-types.d.ts +45 -6
- package/dist/runtime/cli-add-types.js +2 -0
- package/dist/runtime/cli-add-validation.d.ts +7 -0
- package/dist/runtime/cli-add-validation.js +9 -0
- package/dist/runtime/cli-add-workspace-ability-anchors.d.ts +24 -0
- package/dist/runtime/cli-add-workspace-ability-anchors.js +294 -0
- package/dist/runtime/cli-add-workspace-ability-registry.d.ts +10 -0
- package/dist/runtime/cli-add-workspace-ability-registry.js +51 -0
- package/dist/runtime/cli-add-workspace-ability-scaffold.d.ts +1 -1
- package/dist/runtime/cli-add-workspace-ability-scaffold.js +5 -308
- package/dist/runtime/cli-add-workspace-admin-view-scaffold.js +6 -2
- package/dist/runtime/cli-add-workspace-admin-view-templates-core-data.d.ts +34 -0
- package/dist/runtime/cli-add-workspace-admin-view-templates-core-data.js +483 -0
- package/dist/runtime/cli-add-workspace-admin-view-templates-default.d.ts +30 -0
- package/dist/runtime/cli-add-workspace-admin-view-templates-default.js +310 -0
- package/dist/runtime/cli-add-workspace-admin-view-templates-rest.d.ts +25 -0
- package/dist/runtime/cli-add-workspace-admin-view-templates-rest.js +124 -0
- package/dist/runtime/cli-add-workspace-admin-view-templates-settings.d.ts +34 -0
- package/dist/runtime/cli-add-workspace-admin-view-templates-settings.js +370 -0
- package/dist/runtime/cli-add-workspace-admin-view-templates-shared.d.ts +49 -0
- package/dist/runtime/cli-add-workspace-admin-view-templates-shared.js +259 -0
- package/dist/runtime/cli-add-workspace-admin-view-templates.d.ts +18 -27
- package/dist/runtime/cli-add-workspace-admin-view-templates.js +30 -1326
- package/dist/runtime/cli-add-workspace-ai-anchors.d.ts +4 -4
- package/dist/runtime/cli-add-workspace-ai-anchors.js +8 -233
- package/dist/runtime/cli-add-workspace-ai-scaffold.js +4 -2
- package/dist/runtime/cli-add-workspace-ai-source-emitters.d.ts +1 -4
- package/dist/runtime/cli-add-workspace-ai-source-emitters.js +1 -129
- package/dist/runtime/cli-add-workspace-ai-sync-rest-anchors.d.ts +5 -0
- package/dist/runtime/cli-add-workspace-ai-sync-rest-anchors.js +236 -0
- package/dist/runtime/cli-add-workspace-ai-sync-script-source.d.ts +4 -0
- package/dist/runtime/cli-add-workspace-ai-sync-script-source.js +145 -0
- package/dist/runtime/cli-add-workspace-assets.d.ts +6 -63
- package/dist/runtime/cli-add-workspace-assets.js +6 -950
- package/dist/runtime/cli-add-workspace-binding-source-anchors.d.ts +23 -0
- package/dist/runtime/cli-add-workspace-binding-source-anchors.js +112 -0
- package/dist/runtime/cli-add-workspace-binding-source-source-emitters.d.ts +33 -0
- package/dist/runtime/cli-add-workspace-binding-source-source-emitters.js +436 -0
- package/dist/runtime/cli-add-workspace-binding-source-types.d.ts +20 -0
- package/dist/runtime/cli-add-workspace-binding-source-types.js +1 -0
- package/dist/runtime/cli-add-workspace-binding-source.d.ts +40 -0
- package/dist/runtime/cli-add-workspace-binding-source.js +275 -0
- package/dist/runtime/cli-add-workspace-block-style.d.ts +22 -0
- package/dist/runtime/cli-add-workspace-block-style.js +148 -0
- package/dist/runtime/cli-add-workspace-block-transform.d.ts +32 -0
- package/dist/runtime/cli-add-workspace-block-transform.js +197 -0
- package/dist/runtime/cli-add-workspace-contract.js +1 -1
- package/dist/runtime/cli-add-workspace-core-variation.d.ts +20 -0
- package/dist/runtime/cli-add-workspace-core-variation.js +322 -0
- package/dist/runtime/cli-add-workspace-editor-plugin-anchors.d.ts +37 -0
- package/dist/runtime/cli-add-workspace-editor-plugin-anchors.js +206 -0
- package/dist/runtime/cli-add-workspace-editor-plugin-source-emitters.d.ts +47 -0
- package/dist/runtime/cli-add-workspace-editor-plugin-source-emitters.js +219 -0
- package/dist/runtime/cli-add-workspace-editor-plugin.d.ts +22 -0
- package/dist/runtime/cli-add-workspace-editor-plugin.js +78 -0
- package/dist/runtime/cli-add-workspace-hooked-block.d.ts +23 -0
- package/dist/runtime/cli-add-workspace-hooked-block.js +57 -0
- package/dist/runtime/cli-add-workspace-integration-env-files.d.ts +33 -0
- package/dist/runtime/cli-add-workspace-integration-env-files.js +65 -0
- package/dist/runtime/cli-add-workspace-integration-env-package-json.d.ts +38 -0
- package/dist/runtime/cli-add-workspace-integration-env-package-json.js +122 -0
- package/dist/runtime/cli-add-workspace-integration-env-source-emitters.d.ts +44 -0
- package/dist/runtime/cli-add-workspace-integration-env-source-emitters.js +262 -0
- package/dist/runtime/cli-add-workspace-integration-env.d.ts +3 -1
- package/dist/runtime/cli-add-workspace-integration-env.js +10 -313
- package/dist/runtime/cli-add-workspace-pattern-anchors.d.ts +10 -0
- package/dist/runtime/cli-add-workspace-pattern-anchors.js +95 -0
- package/dist/runtime/cli-add-workspace-pattern-options.d.ts +20 -0
- package/dist/runtime/cli-add-workspace-pattern-options.js +113 -0
- package/dist/runtime/cli-add-workspace-pattern-source-emitters.d.ts +20 -0
- package/dist/runtime/cli-add-workspace-pattern-source-emitters.js +57 -0
- package/dist/runtime/cli-add-workspace-pattern.d.ts +42 -0
- package/dist/runtime/cli-add-workspace-pattern.js +99 -0
- package/dist/runtime/cli-add-workspace-post-meta.js +1 -1
- package/dist/runtime/cli-add-workspace-registration-hooks.d.ts +50 -0
- package/dist/runtime/cli-add-workspace-registration-hooks.js +162 -0
- package/dist/runtime/cli-add-workspace-rest-anchors.d.ts +9 -4
- package/dist/runtime/cli-add-workspace-rest-anchors.js +9 -428
- package/dist/runtime/cli-add-workspace-rest-bootstrap-anchors.d.ts +17 -0
- package/dist/runtime/cli-add-workspace-rest-bootstrap-anchors.js +108 -0
- package/dist/runtime/cli-add-workspace-rest-contract-sync-anchors.d.ts +9 -0
- package/dist/runtime/cli-add-workspace-rest-contract-sync-anchors.js +142 -0
- package/dist/runtime/cli-add-workspace-rest-generated-source-emitters.d.ts +51 -0
- package/dist/runtime/cli-add-workspace-rest-generated-source-emitters.js +415 -0
- package/dist/runtime/cli-add-workspace-rest-generated.d.ts +9 -0
- package/dist/runtime/cli-add-workspace-rest-generated.js +160 -0
- package/dist/runtime/cli-add-workspace-rest-manual-source-emitters.d.ts +80 -0
- package/dist/runtime/cli-add-workspace-rest-manual-source-emitters.js +238 -0
- package/dist/runtime/cli-add-workspace-rest-manual.d.ts +8 -0
- package/dist/runtime/cli-add-workspace-rest-manual.js +266 -0
- package/dist/runtime/cli-add-workspace-rest-php-templates.d.ts +18 -0
- package/dist/runtime/cli-add-workspace-rest-php-templates.js +359 -0
- package/dist/runtime/cli-add-workspace-rest-resource-php-routing-template.d.ts +33 -0
- package/dist/runtime/cli-add-workspace-rest-resource-php-routing-template.js +145 -0
- package/dist/runtime/cli-add-workspace-rest-resource-sync-anchors.d.ts +9 -0
- package/dist/runtime/cli-add-workspace-rest-resource-sync-anchors.js +162 -0
- package/dist/runtime/cli-add-workspace-rest-schema-helper-php-template.d.ts +7 -0
- package/dist/runtime/cli-add-workspace-rest-schema-helper-php-template.js +193 -0
- package/dist/runtime/cli-add-workspace-rest-source-emitters.d.ts +5 -91
- package/dist/runtime/cli-add-workspace-rest-source-emitters.js +5 -642
- package/dist/runtime/cli-add-workspace-rest-source-utils.d.ts +17 -0
- package/dist/runtime/cli-add-workspace-rest-source-utils.js +50 -0
- package/dist/runtime/cli-add-workspace-rest-sync-script-shared.d.ts +56 -0
- package/dist/runtime/cli-add-workspace-rest-sync-script-shared.js +122 -0
- package/dist/runtime/cli-add-workspace-rest-types.d.ts +108 -0
- package/dist/runtime/cli-add-workspace-rest-types.js +1 -0
- package/dist/runtime/cli-add-workspace-rest.d.ts +3 -20
- package/dist/runtime/cli-add-workspace-rest.js +33 -788
- package/dist/runtime/cli-add-workspace-variation.d.ts +22 -0
- package/dist/runtime/cli-add-workspace-variation.js +162 -0
- package/dist/runtime/cli-add-workspace.d.ts +42 -107
- package/dist/runtime/cli-add-workspace.js +42 -674
- package/dist/runtime/cli-add.d.ts +3 -3
- package/dist/runtime/cli-add.js +2 -2
- package/dist/runtime/cli-core.d.ts +3 -2
- package/dist/runtime/cli-core.js +2 -2
- package/dist/runtime/cli-diagnostics.d.ts +3 -1
- package/dist/runtime/cli-diagnostics.js +17 -5
- package/dist/runtime/cli-doctor-workspace-bindings.js +63 -1
- package/dist/runtime/cli-doctor-workspace-block-addons.d.ts +12 -0
- package/dist/runtime/cli-doctor-workspace-block-addons.js +162 -0
- package/dist/runtime/cli-doctor-workspace-block-iframe.d.ts +9 -0
- package/dist/runtime/cli-doctor-workspace-block-iframe.js +228 -0
- package/dist/runtime/cli-doctor-workspace-block-metadata.d.ts +11 -0
- package/dist/runtime/cli-doctor-workspace-block-metadata.js +111 -0
- package/dist/runtime/cli-doctor-workspace-blocks.js +6 -424
- package/dist/runtime/cli-doctor-workspace-features-abilities.d.ts +11 -0
- package/dist/runtime/cli-doctor-workspace-features-abilities.js +112 -0
- package/dist/runtime/cli-doctor-workspace-features-admin-views.d.ts +11 -0
- package/dist/runtime/cli-doctor-workspace-features-admin-views.js +128 -0
- package/dist/runtime/cli-doctor-workspace-features-ai.d.ts +11 -0
- package/dist/runtime/cli-doctor-workspace-features-ai.js +57 -0
- package/dist/runtime/cli-doctor-workspace-features-editor-plugins.d.ts +11 -0
- package/dist/runtime/cli-doctor-workspace-features-editor-plugins.js +80 -0
- package/dist/runtime/cli-doctor-workspace-features-post-meta.d.ts +11 -0
- package/dist/runtime/cli-doctor-workspace-features-post-meta.js +77 -0
- package/dist/runtime/cli-doctor-workspace-features-rest.d.ts +11 -0
- package/dist/runtime/cli-doctor-workspace-features-rest.js +120 -0
- package/dist/runtime/cli-doctor-workspace-features.js +14 -487
- package/dist/runtime/cli-doctor.d.ts +54 -3
- package/dist/runtime/cli-doctor.js +92 -10
- package/dist/runtime/cli-help.js +12 -7
- package/dist/runtime/cli-init-package-json.js +4 -2
- package/dist/runtime/cli-prompt.d.ts +16 -2
- package/dist/runtime/cli-prompt.js +29 -12
- package/dist/runtime/cli-scaffold.d.ts +2 -1
- package/dist/runtime/cli-scaffold.js +19 -10
- package/dist/runtime/external-template-guards.js +4 -6
- package/dist/runtime/index.d.ts +6 -3
- package/dist/runtime/index.js +4 -2
- package/dist/runtime/json-utils.d.ts +62 -4
- package/dist/runtime/json-utils.js +78 -4
- package/dist/runtime/local-dev-presets.js +6 -2
- package/dist/runtime/migration-ui-capability.js +4 -1
- package/dist/runtime/migration-utils.js +4 -1
- package/dist/runtime/package-managers.js +6 -1
- package/dist/runtime/package-versions.d.ts +1 -0
- package/dist/runtime/package-versions.js +16 -3
- package/dist/runtime/pattern-catalog.d.ts +122 -0
- package/dist/runtime/pattern-catalog.js +471 -0
- package/dist/runtime/post-meta-binding-fields.d.ts +46 -0
- package/dist/runtime/post-meta-binding-fields.js +135 -0
- package/dist/runtime/scaffold-bootstrap.js +7 -2
- package/dist/runtime/scaffold-package-manager-files.js +5 -1
- package/dist/runtime/scaffold-repository-reference.js +4 -2
- package/dist/runtime/scaffold-template-variables.js +2 -1
- package/dist/runtime/scaffold.d.ts +18 -1
- package/dist/runtime/scaffold.js +55 -2
- package/dist/runtime/temp-roots.js +4 -1
- package/dist/runtime/template-layers.js +4 -1
- package/dist/runtime/template-registry.js +9 -3
- package/dist/runtime/template-source-contracts.d.ts +2 -0
- package/dist/runtime/template-source-normalization.js +2 -1
- package/dist/runtime/template-source-remote.js +18 -5
- package/dist/runtime/template-source-seeds.js +10 -3
- package/dist/runtime/typia-llm-json-schema.d.ts +24 -0
- package/dist/runtime/typia-llm-json-schema.js +33 -0
- package/dist/runtime/typia-llm-openapi-constraints.d.ts +20 -0
- package/dist/runtime/typia-llm-openapi-constraints.js +254 -0
- package/dist/runtime/typia-llm-projection.d.ts +25 -0
- package/dist/runtime/typia-llm-projection.js +58 -0
- package/dist/runtime/typia-llm-render.d.ts +21 -0
- package/dist/runtime/typia-llm-render.js +252 -0
- package/dist/runtime/typia-llm-sync.d.ts +10 -0
- package/dist/runtime/typia-llm-sync.js +63 -0
- package/dist/runtime/typia-llm-types.d.ts +197 -0
- package/dist/runtime/typia-llm-types.js +1 -0
- package/dist/runtime/typia-llm.d.ts +9 -255
- package/dist/runtime/typia-llm.js +5 -634
- package/dist/runtime/workspace-inventory-mutations.js +15 -1
- package/dist/runtime/workspace-inventory-parser-entries.d.ts +17 -0
- package/dist/runtime/workspace-inventory-parser-entries.js +157 -0
- package/dist/runtime/workspace-inventory-parser-validation.d.ts +104 -0
- package/dist/runtime/workspace-inventory-parser-validation.js +34 -0
- package/dist/runtime/workspace-inventory-parser.d.ts +3 -45
- package/dist/runtime/workspace-inventory-parser.js +3 -581
- package/dist/runtime/workspace-inventory-section-descriptors.d.ts +19 -0
- package/dist/runtime/workspace-inventory-section-descriptors.js +443 -0
- package/dist/runtime/workspace-inventory-templates.d.ts +3 -3
- package/dist/runtime/workspace-inventory-templates.js +10 -1
- package/dist/runtime/workspace-inventory-types.d.ts +10 -1
- package/dist/runtime/workspace-project.js +4 -6
- package/package.json +8 -3
- package/templates/_shared/compound/core/scripts/block-config.ts.mustache +22 -0
- package/templates/_shared/compound/core/scripts/sync-types-to-block-json.ts.mustache +103 -2
- package/templates/_shared/compound/core/src/inner-blocks-templates.ts.mustache +13 -0
- package/templates/_shared/compound/persistence/scripts/block-config.ts.mustache +22 -1
|
@@ -0,0 +1,471 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { validateBlockPatternContentNesting, } from "@wp-typia/block-runtime/metadata-core";
|
|
4
|
+
export const PATTERN_CATALOG_SCOPE_IDS = ["full", "section"];
|
|
5
|
+
const PATTERN_SLUG_PATTERN = /^[a-z][a-z0-9-]*$/u;
|
|
6
|
+
const PATTERN_SECTION_ROLE_PATTERN = PATTERN_SLUG_PATTERN;
|
|
7
|
+
const PATTERN_TAG_PATTERN = /^[a-z0-9][a-z0-9-]*$/u;
|
|
8
|
+
const PATTERN_CONTENT_FILE_ROOT = "src/patterns/";
|
|
9
|
+
const DEFAULT_SECTION_ROLE_CONVENTION = {
|
|
10
|
+
baseClassName: "section",
|
|
11
|
+
requireUniqueFullPatternRoles: false,
|
|
12
|
+
roleAttributePaths: ["metadata.sectionRole"],
|
|
13
|
+
roleClassNamePattern: "section--{role}",
|
|
14
|
+
wrapperBlockName: "core/group",
|
|
15
|
+
};
|
|
16
|
+
function createPatternCatalogDiagnostic(diagnostic) {
|
|
17
|
+
return diagnostic;
|
|
18
|
+
}
|
|
19
|
+
function isPatternCatalogScope(value) {
|
|
20
|
+
return PATTERN_CATALOG_SCOPE_IDS.includes(value);
|
|
21
|
+
}
|
|
22
|
+
function isSafeRelativePath(value) {
|
|
23
|
+
return (value.length > 0 &&
|
|
24
|
+
!path.isAbsolute(value) &&
|
|
25
|
+
!value.includes("\\") &&
|
|
26
|
+
!value.split(/[\\/]+/u).includes("..") &&
|
|
27
|
+
!/[<>:"|?*\u0000-\u001F]/u.test(value));
|
|
28
|
+
}
|
|
29
|
+
function isPatternContentFilePath(value) {
|
|
30
|
+
if (!isSafeRelativePath(value) ||
|
|
31
|
+
!value.startsWith(PATTERN_CONTENT_FILE_ROOT) ||
|
|
32
|
+
!value.endsWith(".php")) {
|
|
33
|
+
return false;
|
|
34
|
+
}
|
|
35
|
+
const patternRelativePath = value.slice(PATTERN_CONTENT_FILE_ROOT.length);
|
|
36
|
+
const segments = patternRelativePath.split("/");
|
|
37
|
+
return ((segments.length === 1 || segments.length === 2) &&
|
|
38
|
+
segments.every((segment) => segment.length > 0));
|
|
39
|
+
}
|
|
40
|
+
function normalizeSectionRoleConventionInput(convention = {}) {
|
|
41
|
+
return {
|
|
42
|
+
baseClassName: convention.baseClassName ??
|
|
43
|
+
DEFAULT_SECTION_ROLE_CONVENTION.baseClassName,
|
|
44
|
+
requireUniqueFullPatternRoles: convention.requireUniqueFullPatternRoles ??
|
|
45
|
+
DEFAULT_SECTION_ROLE_CONVENTION.requireUniqueFullPatternRoles,
|
|
46
|
+
roleAttributePaths: convention.roleAttributePaths ??
|
|
47
|
+
DEFAULT_SECTION_ROLE_CONVENTION.roleAttributePaths,
|
|
48
|
+
roleClassNamePattern: convention.roleClassNamePattern ??
|
|
49
|
+
DEFAULT_SECTION_ROLE_CONVENTION.roleClassNamePattern,
|
|
50
|
+
wrapperBlockName: convention.wrapperBlockName ??
|
|
51
|
+
DEFAULT_SECTION_ROLE_CONVENTION.wrapperBlockName,
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
function escapeRegExp(value) {
|
|
55
|
+
return value.replace(/[.*+?^${}()|[\]\\]/gu, "\\$&");
|
|
56
|
+
}
|
|
57
|
+
function createRoleClassNamePattern(pattern) {
|
|
58
|
+
const parts = pattern.split("{role}");
|
|
59
|
+
if (parts.length !== 2) {
|
|
60
|
+
throw new Error(`roleClassNamePattern must contain exactly one "{role}" placeholder.`);
|
|
61
|
+
}
|
|
62
|
+
return new RegExp(`^${escapeRegExp(parts[0] ?? "")}(?<role>\\S*)${escapeRegExp(parts[1] ?? "")}$`, "u");
|
|
63
|
+
}
|
|
64
|
+
function normalizeSectionRoleConvention(convention = {}) {
|
|
65
|
+
const normalized = normalizeSectionRoleConventionInput(convention);
|
|
66
|
+
return {
|
|
67
|
+
...normalized,
|
|
68
|
+
roleClassNamePatternRegExp: createRoleClassNamePattern(normalized.roleClassNamePattern),
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
function getClassNameTokens(attributes) {
|
|
72
|
+
const className = attributes.className;
|
|
73
|
+
if (typeof className !== "string") {
|
|
74
|
+
return [];
|
|
75
|
+
}
|
|
76
|
+
return className.split(/\s+/u).filter((token) => token.length > 0);
|
|
77
|
+
}
|
|
78
|
+
function getAttributePathValue(attributes, pathName) {
|
|
79
|
+
return pathName.split(".").reduce((current, segment) => {
|
|
80
|
+
if (current !== null &&
|
|
81
|
+
typeof current === "object" &&
|
|
82
|
+
!Array.isArray(current) &&
|
|
83
|
+
Object.prototype.hasOwnProperty.call(current, segment)) {
|
|
84
|
+
return current[segment];
|
|
85
|
+
}
|
|
86
|
+
return undefined;
|
|
87
|
+
}, attributes);
|
|
88
|
+
}
|
|
89
|
+
function collectStringValues(value) {
|
|
90
|
+
if (typeof value === "string") {
|
|
91
|
+
return [value];
|
|
92
|
+
}
|
|
93
|
+
if (Array.isArray(value)) {
|
|
94
|
+
return value.filter((item) => typeof item === "string");
|
|
95
|
+
}
|
|
96
|
+
return [];
|
|
97
|
+
}
|
|
98
|
+
function uniqueValues(values) {
|
|
99
|
+
return [...new Set(values)];
|
|
100
|
+
}
|
|
101
|
+
function formatRoleList(roles) {
|
|
102
|
+
if (roles.length === 0) {
|
|
103
|
+
return "none";
|
|
104
|
+
}
|
|
105
|
+
return roles.map((role) => `"${role}"`).join(", ");
|
|
106
|
+
}
|
|
107
|
+
function formatBlockPaths(paths) {
|
|
108
|
+
return paths.map((blockPath) => `at ${blockPath}`).join(", ");
|
|
109
|
+
}
|
|
110
|
+
function describeRoleMarkerConvention(convention) {
|
|
111
|
+
return `${convention.wrapperBlockName} wrappers with class "${convention.roleClassNamePattern}" or attributes ${convention.roleAttributePaths.join(", ")}`;
|
|
112
|
+
}
|
|
113
|
+
function isSectionWrapperCandidate(block, roles, convention) {
|
|
114
|
+
if (block.blockName !== convention.wrapperBlockName) {
|
|
115
|
+
return false;
|
|
116
|
+
}
|
|
117
|
+
if (roles.length > 0) {
|
|
118
|
+
return true;
|
|
119
|
+
}
|
|
120
|
+
return getClassNameTokens(block.attributes).includes(convention.baseClassName);
|
|
121
|
+
}
|
|
122
|
+
function collectSectionRoleMatches(blocks, convention, pathSegments = []) {
|
|
123
|
+
return blocks.flatMap((block, index) => {
|
|
124
|
+
const blockPathSegments = [
|
|
125
|
+
...pathSegments,
|
|
126
|
+
`${block.blockName}[${index}]`,
|
|
127
|
+
];
|
|
128
|
+
const blockPath = blockPathSegments.join(" > ");
|
|
129
|
+
const roles = extractPatternSectionRolesFromAttributes(block.attributes, convention);
|
|
130
|
+
const matches = isSectionWrapperCandidate(block, roles, convention)
|
|
131
|
+
? [
|
|
132
|
+
{
|
|
133
|
+
blockName: block.blockName,
|
|
134
|
+
blockPath,
|
|
135
|
+
roles,
|
|
136
|
+
},
|
|
137
|
+
]
|
|
138
|
+
: [];
|
|
139
|
+
return [
|
|
140
|
+
...matches,
|
|
141
|
+
...collectSectionRoleMatches(block.innerBlocks, convention, blockPathSegments),
|
|
142
|
+
];
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
function unescapeSerializedBlockCommentJsonQuotes(content) {
|
|
146
|
+
return content.replace(/<!--([\s\S]*?)-->/gu, (comment, body) => {
|
|
147
|
+
const source = body.trim();
|
|
148
|
+
if (!source.startsWith("wp:") && !source.startsWith("/wp:")) {
|
|
149
|
+
return comment;
|
|
150
|
+
}
|
|
151
|
+
return `<!--${body.replace(/\\"/gu, '"')}-->`;
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
/**
|
|
155
|
+
* Extract section role slugs from serialized block attributes using the
|
|
156
|
+
* configured class and metadata marker convention.
|
|
157
|
+
*
|
|
158
|
+
* @param attributes Parsed block attributes from serialized pattern content.
|
|
159
|
+
* @param convention Optional marker convention override.
|
|
160
|
+
* @returns Unique role marker values in discovery order.
|
|
161
|
+
*/
|
|
162
|
+
export function extractPatternSectionRolesFromAttributes(attributes, convention = {}) {
|
|
163
|
+
const normalized = normalizeSectionRoleConvention(convention);
|
|
164
|
+
const classRoles = getClassNameTokens(attributes)
|
|
165
|
+
.map((token) => normalized.roleClassNamePatternRegExp.exec(token)?.groups?.role)
|
|
166
|
+
.filter((role) => typeof role === "string");
|
|
167
|
+
const attributeRoles = normalized.roleAttributePaths.flatMap((pathName) => collectStringValues(getAttributePathValue(attributes, pathName)));
|
|
168
|
+
return uniqueValues([...classRoles, ...attributeRoles]);
|
|
169
|
+
}
|
|
170
|
+
/**
|
|
171
|
+
* Find section wrapper blocks and their role markers in parsed pattern content.
|
|
172
|
+
*
|
|
173
|
+
* @param blocks Parsed block tree returned by `validateBlockPatternContentNesting`.
|
|
174
|
+
* @param convention Optional marker convention override.
|
|
175
|
+
* @returns Section wrapper matches with serialized block paths.
|
|
176
|
+
*/
|
|
177
|
+
export function extractPatternSectionRoleMatches(blocks, convention = {}) {
|
|
178
|
+
return collectSectionRoleMatches(blocks, normalizeSectionRoleConvention(convention));
|
|
179
|
+
}
|
|
180
|
+
/**
|
|
181
|
+
* Validate pattern thumbnail references with the same URL/path rules used by
|
|
182
|
+
* catalog diagnostics and `wp-typia add pattern`.
|
|
183
|
+
*
|
|
184
|
+
* @param value Candidate thumbnail URL or relative project path.
|
|
185
|
+
* @returns Whether the value is an http(s) URL or safe relative project path.
|
|
186
|
+
*/
|
|
187
|
+
export function isValidPatternThumbnailUrl(value) {
|
|
188
|
+
if (value.length === 0) {
|
|
189
|
+
return false;
|
|
190
|
+
}
|
|
191
|
+
try {
|
|
192
|
+
const url = new URL(value);
|
|
193
|
+
return url.protocol === "http:" || url.protocol === "https:";
|
|
194
|
+
}
|
|
195
|
+
catch {
|
|
196
|
+
return isSafeRelativePath(value);
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
export function resolvePatternCatalogContentFile(pattern) {
|
|
200
|
+
return pattern.contentFile ?? pattern.file;
|
|
201
|
+
}
|
|
202
|
+
function createKnownSectionRoleSet(patterns) {
|
|
203
|
+
return new Set(patterns
|
|
204
|
+
.map((pattern) => pattern.sectionRole)
|
|
205
|
+
.filter((sectionRole) => typeof sectionRole === "string" &&
|
|
206
|
+
PATTERN_SECTION_ROLE_PATTERN.test(sectionRole)));
|
|
207
|
+
}
|
|
208
|
+
function validatePatternContentSectionRoles({ content, contentFile, convention, knownSectionRoles, label, pattern, }) {
|
|
209
|
+
const diagnostics = [];
|
|
210
|
+
const parsed = validateBlockPatternContentNesting(content, {
|
|
211
|
+
allowExternalBlockNames: true,
|
|
212
|
+
nesting: {},
|
|
213
|
+
patternFile: contentFile,
|
|
214
|
+
});
|
|
215
|
+
let matches = collectSectionRoleMatches(parsed.blocks, convention);
|
|
216
|
+
const unescapedContent = unescapeSerializedBlockCommentJsonQuotes(content);
|
|
217
|
+
if (unescapedContent !== content) {
|
|
218
|
+
const unescapedParsed = validateBlockPatternContentNesting(unescapedContent, {
|
|
219
|
+
allowExternalBlockNames: true,
|
|
220
|
+
nesting: {},
|
|
221
|
+
patternFile: contentFile,
|
|
222
|
+
});
|
|
223
|
+
const unescapedMatches = collectSectionRoleMatches(unescapedParsed.blocks, convention);
|
|
224
|
+
if (unescapedMatches.some((match) => match.roles.length > 0) ||
|
|
225
|
+
matches.length === 0) {
|
|
226
|
+
matches = unescapedMatches;
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
const locatedRoles = matches.flatMap((match) => match.roles.map((role) => ({
|
|
230
|
+
blockPath: match.blockPath,
|
|
231
|
+
role,
|
|
232
|
+
})));
|
|
233
|
+
const invalidRoles = locatedRoles.filter(({ role }) => !PATTERN_SECTION_ROLE_PATTERN.test(role));
|
|
234
|
+
for (const invalidRole of uniqueValues(invalidRoles.map(({ role }) => role))) {
|
|
235
|
+
const paths = invalidRoles
|
|
236
|
+
.filter(({ role }) => role === invalidRole)
|
|
237
|
+
.map(({ blockPath }) => blockPath);
|
|
238
|
+
diagnostics.push(createPatternCatalogDiagnostic({
|
|
239
|
+
code: "invalid-pattern-section-role-marker",
|
|
240
|
+
message: `${label}: section role marker "${invalidRole}" in ${contentFile} must start with a lowercase letter and contain only lowercase letters, numbers, and hyphens (${formatBlockPaths(paths)}).`,
|
|
241
|
+
patternSlug: pattern.slug,
|
|
242
|
+
severity: "error",
|
|
243
|
+
}));
|
|
244
|
+
}
|
|
245
|
+
const validLocatedRoles = locatedRoles.filter(({ role }) => PATTERN_SECTION_ROLE_PATTERN.test(role));
|
|
246
|
+
const validRoles = validLocatedRoles.map(({ role }) => role);
|
|
247
|
+
const uniqueValidRoles = uniqueValues(validRoles);
|
|
248
|
+
const unknownRoles = uniqueValidRoles.filter((role) => !knownSectionRoles.has(role));
|
|
249
|
+
for (const unknownRole of unknownRoles) {
|
|
250
|
+
const paths = validLocatedRoles
|
|
251
|
+
.filter(({ role }) => role === unknownRole)
|
|
252
|
+
.map(({ blockPath }) => blockPath);
|
|
253
|
+
diagnostics.push(createPatternCatalogDiagnostic({
|
|
254
|
+
code: "unknown-pattern-section-role-marker",
|
|
255
|
+
message: `${label}: section role marker "${unknownRole}" in ${contentFile} is not declared by any PATTERNS sectionRole (${formatBlockPaths(paths)}).`,
|
|
256
|
+
patternSlug: pattern.slug,
|
|
257
|
+
severity: "warning",
|
|
258
|
+
}));
|
|
259
|
+
}
|
|
260
|
+
const scope = pattern.scope ?? "full";
|
|
261
|
+
const expectedSectionRole = typeof pattern.sectionRole === "string" &&
|
|
262
|
+
PATTERN_SECTION_ROLE_PATTERN.test(pattern.sectionRole)
|
|
263
|
+
? pattern.sectionRole
|
|
264
|
+
: undefined;
|
|
265
|
+
if (scope === "section" && expectedSectionRole) {
|
|
266
|
+
if (validRoles.length === 0) {
|
|
267
|
+
diagnostics.push(createPatternCatalogDiagnostic({
|
|
268
|
+
code: "missing-pattern-section-role-marker",
|
|
269
|
+
message: `${label}: section-scoped pattern content in ${contentFile} must include section role marker "${expectedSectionRole}" using ${describeRoleMarkerConvention(convention)}.`,
|
|
270
|
+
patternSlug: pattern.slug,
|
|
271
|
+
severity: "error",
|
|
272
|
+
}));
|
|
273
|
+
}
|
|
274
|
+
else if (!validRoles.includes(expectedSectionRole)) {
|
|
275
|
+
diagnostics.push(createPatternCatalogDiagnostic({
|
|
276
|
+
code: "mismatched-pattern-section-role",
|
|
277
|
+
message: `${label}: manifest sectionRole "${expectedSectionRole}" was not found in ${contentFile}; found ${formatRoleList(uniqueValidRoles)}.`,
|
|
278
|
+
patternSlug: pattern.slug,
|
|
279
|
+
severity: "error",
|
|
280
|
+
}));
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
if (scope === "full" && convention.requireUniqueFullPatternRoles) {
|
|
284
|
+
for (const role of uniqueValidRoles) {
|
|
285
|
+
const paths = validLocatedRoles
|
|
286
|
+
.filter((locatedRole) => locatedRole.role === role)
|
|
287
|
+
.map(({ blockPath }) => blockPath);
|
|
288
|
+
if (paths.length <= 1) {
|
|
289
|
+
continue;
|
|
290
|
+
}
|
|
291
|
+
diagnostics.push(createPatternCatalogDiagnostic({
|
|
292
|
+
code: "duplicate-pattern-section-role-marker",
|
|
293
|
+
message: `${label}: full pattern content in ${contentFile} repeats section role marker "${role}" (${formatBlockPaths(paths)}).`,
|
|
294
|
+
patternSlug: pattern.slug,
|
|
295
|
+
severity: "warning",
|
|
296
|
+
}));
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
return diagnostics;
|
|
300
|
+
}
|
|
301
|
+
/**
|
|
302
|
+
* Validate typed pattern catalog metadata declared in `scripts/block-config.ts`.
|
|
303
|
+
*
|
|
304
|
+
* The validator checks catalog shape, duplicate slugs, optional section-role
|
|
305
|
+
* rules, safe content file paths, and missing files when a workspace root is
|
|
306
|
+
* provided. With a workspace root, it also parses serialized block markup and
|
|
307
|
+
* compares section role markers against the catalog manifest.
|
|
308
|
+
*
|
|
309
|
+
* @param patterns Pattern catalog entries to validate.
|
|
310
|
+
* @param options Optional project root and section role marker convention.
|
|
311
|
+
* @returns Structured diagnostics split into errors and warnings.
|
|
312
|
+
*/
|
|
313
|
+
export function validatePatternCatalog(patterns, options = {}) {
|
|
314
|
+
const diagnostics = [];
|
|
315
|
+
const seenSlugs = new Map();
|
|
316
|
+
let sectionRoleConvention = null;
|
|
317
|
+
if (options.sectionRoleConvention !== false) {
|
|
318
|
+
try {
|
|
319
|
+
sectionRoleConvention = normalizeSectionRoleConvention(options.sectionRoleConvention);
|
|
320
|
+
}
|
|
321
|
+
catch (error) {
|
|
322
|
+
diagnostics.push(createPatternCatalogDiagnostic({
|
|
323
|
+
code: "invalid-pattern-section-role-convention",
|
|
324
|
+
message: `sectionRoleConvention.roleClassNamePattern is invalid: ${error instanceof Error ? error.message : String(error)}`,
|
|
325
|
+
severity: "error",
|
|
326
|
+
}));
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
const knownSectionRoles = createKnownSectionRoleSet(patterns);
|
|
330
|
+
for (const [index, pattern] of patterns.entries()) {
|
|
331
|
+
const label = pattern.slug || `PATTERNS[${index}]`;
|
|
332
|
+
if (!PATTERN_SLUG_PATTERN.test(pattern.slug)) {
|
|
333
|
+
diagnostics.push(createPatternCatalogDiagnostic({
|
|
334
|
+
code: "invalid-pattern-slug",
|
|
335
|
+
message: `${label}: slug must start with a lowercase letter and contain only lowercase letters, numbers, and hyphens.`,
|
|
336
|
+
patternSlug: pattern.slug,
|
|
337
|
+
severity: "error",
|
|
338
|
+
}));
|
|
339
|
+
}
|
|
340
|
+
const previousIndex = seenSlugs.get(pattern.slug);
|
|
341
|
+
if (previousIndex !== undefined) {
|
|
342
|
+
diagnostics.push(createPatternCatalogDiagnostic({
|
|
343
|
+
code: "duplicate-pattern-slug",
|
|
344
|
+
message: `${label}: duplicate slug already declared at PATTERNS[${previousIndex}].`,
|
|
345
|
+
patternSlug: pattern.slug,
|
|
346
|
+
severity: "error",
|
|
347
|
+
}));
|
|
348
|
+
}
|
|
349
|
+
else {
|
|
350
|
+
seenSlugs.set(pattern.slug, index);
|
|
351
|
+
}
|
|
352
|
+
const scope = pattern.scope ?? "full";
|
|
353
|
+
if (!isPatternCatalogScope(scope)) {
|
|
354
|
+
diagnostics.push(createPatternCatalogDiagnostic({
|
|
355
|
+
code: "invalid-pattern-scope",
|
|
356
|
+
message: `${label}: scope must be one of ${PATTERN_CATALOG_SCOPE_IDS.join(", ")}.`,
|
|
357
|
+
patternSlug: pattern.slug,
|
|
358
|
+
severity: "error",
|
|
359
|
+
}));
|
|
360
|
+
}
|
|
361
|
+
if (scope === "section" && !pattern.sectionRole) {
|
|
362
|
+
diagnostics.push(createPatternCatalogDiagnostic({
|
|
363
|
+
code: "missing-pattern-section-role",
|
|
364
|
+
message: `${label}: section-scoped patterns must declare sectionRole.`,
|
|
365
|
+
patternSlug: pattern.slug,
|
|
366
|
+
severity: "error",
|
|
367
|
+
}));
|
|
368
|
+
}
|
|
369
|
+
if (pattern.sectionRole !== undefined &&
|
|
370
|
+
!PATTERN_SECTION_ROLE_PATTERN.test(pattern.sectionRole)) {
|
|
371
|
+
diagnostics.push(createPatternCatalogDiagnostic({
|
|
372
|
+
code: "invalid-pattern-section-role",
|
|
373
|
+
message: `${label}: sectionRole must start with a lowercase letter and contain only lowercase letters, numbers, and hyphens.`,
|
|
374
|
+
patternSlug: pattern.slug,
|
|
375
|
+
severity: "error",
|
|
376
|
+
}));
|
|
377
|
+
}
|
|
378
|
+
for (const [tagIndex, tag] of (pattern.tags ?? []).entries()) {
|
|
379
|
+
if (!PATTERN_TAG_PATTERN.test(tag)) {
|
|
380
|
+
diagnostics.push(createPatternCatalogDiagnostic({
|
|
381
|
+
code: "invalid-pattern-tag",
|
|
382
|
+
message: `${label}: tags[${tagIndex}] must contain only lowercase letters, numbers, and hyphens.`,
|
|
383
|
+
patternSlug: pattern.slug,
|
|
384
|
+
severity: "error",
|
|
385
|
+
}));
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
if (pattern.thumbnailUrl !== undefined &&
|
|
389
|
+
!isValidPatternThumbnailUrl(pattern.thumbnailUrl)) {
|
|
390
|
+
diagnostics.push(createPatternCatalogDiagnostic({
|
|
391
|
+
code: "invalid-pattern-thumbnail-url",
|
|
392
|
+
message: `${label}: thumbnailUrl must be an http(s) URL or safe relative project path.`,
|
|
393
|
+
patternSlug: pattern.slug,
|
|
394
|
+
severity: "error",
|
|
395
|
+
}));
|
|
396
|
+
}
|
|
397
|
+
const contentFile = resolvePatternCatalogContentFile(pattern);
|
|
398
|
+
if (!contentFile) {
|
|
399
|
+
diagnostics.push(createPatternCatalogDiagnostic({
|
|
400
|
+
code: "missing-pattern-content-file",
|
|
401
|
+
message: `${label}: contentFile or legacy file must point at the pattern PHP file.`,
|
|
402
|
+
patternSlug: pattern.slug,
|
|
403
|
+
severity: "error",
|
|
404
|
+
}));
|
|
405
|
+
continue;
|
|
406
|
+
}
|
|
407
|
+
if (!isPatternContentFilePath(contentFile)) {
|
|
408
|
+
diagnostics.push(createPatternCatalogDiagnostic({
|
|
409
|
+
code: "invalid-pattern-content-file",
|
|
410
|
+
message: `${label}: contentFile must be a safe relative project path directly under src/patterns/ or one nested directory under src/patterns/ and end in .php.`,
|
|
411
|
+
patternSlug: pattern.slug,
|
|
412
|
+
severity: "error",
|
|
413
|
+
}));
|
|
414
|
+
continue;
|
|
415
|
+
}
|
|
416
|
+
if (!options.projectDir) {
|
|
417
|
+
continue;
|
|
418
|
+
}
|
|
419
|
+
const absoluteContentFile = path.join(options.projectDir, contentFile);
|
|
420
|
+
if (!fs.existsSync(absoluteContentFile)) {
|
|
421
|
+
diagnostics.push(createPatternCatalogDiagnostic({
|
|
422
|
+
code: "missing-pattern-content-file",
|
|
423
|
+
message: `${label}: missing pattern content file ${contentFile}.`,
|
|
424
|
+
patternSlug: pattern.slug,
|
|
425
|
+
severity: "error",
|
|
426
|
+
}));
|
|
427
|
+
continue;
|
|
428
|
+
}
|
|
429
|
+
if (sectionRoleConvention) {
|
|
430
|
+
let content;
|
|
431
|
+
try {
|
|
432
|
+
content = fs.readFileSync(absoluteContentFile, "utf8");
|
|
433
|
+
}
|
|
434
|
+
catch (error) {
|
|
435
|
+
diagnostics.push(createPatternCatalogDiagnostic({
|
|
436
|
+
code: "invalid-pattern-content-file",
|
|
437
|
+
message: `${label}: failed to read pattern content file ${contentFile}: ${error instanceof Error ? error.message : String(error)}.`,
|
|
438
|
+
patternSlug: pattern.slug,
|
|
439
|
+
severity: "error",
|
|
440
|
+
}));
|
|
441
|
+
continue;
|
|
442
|
+
}
|
|
443
|
+
diagnostics.push(...validatePatternContentSectionRoles({
|
|
444
|
+
content,
|
|
445
|
+
contentFile,
|
|
446
|
+
convention: sectionRoleConvention,
|
|
447
|
+
knownSectionRoles,
|
|
448
|
+
label,
|
|
449
|
+
pattern,
|
|
450
|
+
}));
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
const warnings = diagnostics.filter((diagnostic) => diagnostic.severity === "warning");
|
|
454
|
+
const errors = diagnostics.filter((diagnostic) => diagnostic.severity === "error");
|
|
455
|
+
return {
|
|
456
|
+
diagnostics,
|
|
457
|
+
errors,
|
|
458
|
+
warnings,
|
|
459
|
+
};
|
|
460
|
+
}
|
|
461
|
+
/**
|
|
462
|
+
* Render pattern catalog diagnostics for CLI, sync, and doctor output.
|
|
463
|
+
*
|
|
464
|
+
* @param diagnostics Diagnostics returned from {@link validatePatternCatalog}.
|
|
465
|
+
* @returns Human-readable lines with stable diagnostic codes.
|
|
466
|
+
*/
|
|
467
|
+
export function formatPatternCatalogDiagnostics(diagnostics) {
|
|
468
|
+
return diagnostics
|
|
469
|
+
.map((diagnostic) => `- [${diagnostic.code}] ${diagnostic.message}`)
|
|
470
|
+
.join("\n");
|
|
471
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import type { WorkspacePostMetaInventoryEntry } from "./workspace-inventory-types.js";
|
|
2
|
+
/**
|
|
3
|
+
* Field metadata extracted from a typed post-meta schema for a generated block
|
|
4
|
+
* bindings source.
|
|
5
|
+
*
|
|
6
|
+
* @property fallbackValue Editor preview value used when live post meta is unavailable.
|
|
7
|
+
* @property label Human-readable label derived from the schema property name.
|
|
8
|
+
* @property name Top-level schema property name used as the binding `field` arg.
|
|
9
|
+
* @property required Whether the field is listed in the schema's required array.
|
|
10
|
+
* @property schemaType Resolved JSON Schema type used to choose a preview value.
|
|
11
|
+
*/
|
|
12
|
+
export interface PostMetaBindingField {
|
|
13
|
+
fallbackValue: string;
|
|
14
|
+
label: string;
|
|
15
|
+
name: string;
|
|
16
|
+
required: boolean;
|
|
17
|
+
schemaType: string;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Load and extract binding fields from a post-meta JSON Schema artifact.
|
|
21
|
+
*
|
|
22
|
+
* @param projectDir Workspace root directory.
|
|
23
|
+
* @param postMeta Post-meta inventory entry that points to the schema file.
|
|
24
|
+
* @returns Promise resolving to the extracted top-level binding fields.
|
|
25
|
+
* @throws If the schema cannot be read or does not expose object properties.
|
|
26
|
+
*/
|
|
27
|
+
export declare function loadPostMetaBindingFields(projectDir: string, postMeta: WorkspacePostMetaInventoryEntry): Promise<PostMetaBindingField[]>;
|
|
28
|
+
/**
|
|
29
|
+
* Synchronously load and extract binding fields from a post-meta JSON Schema artifact.
|
|
30
|
+
*
|
|
31
|
+
* @param projectDir Workspace root directory.
|
|
32
|
+
* @param postMeta Post-meta inventory entry that points to the schema file.
|
|
33
|
+
* @returns Extracted top-level binding fields.
|
|
34
|
+
* @throws If the schema cannot be read or does not expose object properties.
|
|
35
|
+
*/
|
|
36
|
+
export declare function loadPostMetaBindingFieldsSync(projectDir: string, postMeta: WorkspacePostMetaInventoryEntry): PostMetaBindingField[];
|
|
37
|
+
/**
|
|
38
|
+
* Validate and resolve a top-level post-meta binding path.
|
|
39
|
+
*
|
|
40
|
+
* @param fields Binding fields extracted from the post-meta schema.
|
|
41
|
+
* @param postMetaSlug Post-meta contract slug used in diagnostics.
|
|
42
|
+
* @param metaPath User-provided field path to validate.
|
|
43
|
+
* @returns The matching binding field.
|
|
44
|
+
* @throws If the path is empty, nested, or missing from the schema fields.
|
|
45
|
+
*/
|
|
46
|
+
export declare function assertPostMetaBindingPath(fields: readonly PostMetaBindingField[], postMetaSlug: string, metaPath: string): PostMetaBindingField;
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import { readJsonFile, readJsonFileSync } from "./json-utils.js";
|
|
3
|
+
const SUPPORTED_SCHEMA_TYPES = new Set([
|
|
4
|
+
"array",
|
|
5
|
+
"boolean",
|
|
6
|
+
"integer",
|
|
7
|
+
"number",
|
|
8
|
+
"object",
|
|
9
|
+
"string",
|
|
10
|
+
]);
|
|
11
|
+
function isJsonRecord(value) {
|
|
12
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
13
|
+
}
|
|
14
|
+
function resolveSchemaType(schema) {
|
|
15
|
+
const type = schema.type;
|
|
16
|
+
if (typeof type === "string" && SUPPORTED_SCHEMA_TYPES.has(type)) {
|
|
17
|
+
return type;
|
|
18
|
+
}
|
|
19
|
+
if (Array.isArray(type) &&
|
|
20
|
+
type.every((entry) => typeof entry === "string")) {
|
|
21
|
+
const nonNullType = type.find((entry) => entry !== "null" && SUPPORTED_SCHEMA_TYPES.has(entry));
|
|
22
|
+
if (nonNullType) {
|
|
23
|
+
return nonNullType;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
if (Array.isArray(schema.enum) && schema.enum.length > 0) {
|
|
27
|
+
return "string";
|
|
28
|
+
}
|
|
29
|
+
return "unknown";
|
|
30
|
+
}
|
|
31
|
+
function resolveFallbackValue(name, schema, schemaType) {
|
|
32
|
+
const enumValue = Array.isArray(schema.enum)
|
|
33
|
+
? schema.enum.find((entry) => typeof entry === "string")
|
|
34
|
+
: undefined;
|
|
35
|
+
if (typeof enumValue === "string") {
|
|
36
|
+
return enumValue;
|
|
37
|
+
}
|
|
38
|
+
switch (schemaType) {
|
|
39
|
+
case "array":
|
|
40
|
+
return "[]";
|
|
41
|
+
case "boolean":
|
|
42
|
+
return "false";
|
|
43
|
+
case "integer":
|
|
44
|
+
case "number":
|
|
45
|
+
return "0";
|
|
46
|
+
case "object":
|
|
47
|
+
return "{}";
|
|
48
|
+
default:
|
|
49
|
+
return `${name} preview`;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
function resolveFieldLabel(name) {
|
|
53
|
+
return name
|
|
54
|
+
.replace(/([a-z0-9])([A-Z])/g, "$1 $2")
|
|
55
|
+
.replace(/[_-]+/g, " ")
|
|
56
|
+
.replace(/\s+/g, " ")
|
|
57
|
+
.trim()
|
|
58
|
+
.replace(/\b\w/g, (match) => match.toUpperCase());
|
|
59
|
+
}
|
|
60
|
+
function extractPostMetaBindingFields(schema, context) {
|
|
61
|
+
if (!isJsonRecord(schema) || !isJsonRecord(schema.properties)) {
|
|
62
|
+
throw new Error(`${context} must expose an object schema with top-level properties before it can back a binding source.`);
|
|
63
|
+
}
|
|
64
|
+
const required = Array.isArray(schema.required)
|
|
65
|
+
? new Set(schema.required.filter((entry) => typeof entry === "string"))
|
|
66
|
+
: new Set();
|
|
67
|
+
const fields = Object.entries(schema.properties).map(([name, propertySchema]) => {
|
|
68
|
+
const property = isJsonRecord(propertySchema) ? propertySchema : {};
|
|
69
|
+
const schemaType = resolveSchemaType(property);
|
|
70
|
+
return {
|
|
71
|
+
fallbackValue: resolveFallbackValue(name, property, schemaType),
|
|
72
|
+
label: resolveFieldLabel(name),
|
|
73
|
+
name,
|
|
74
|
+
required: required.has(name),
|
|
75
|
+
schemaType,
|
|
76
|
+
};
|
|
77
|
+
});
|
|
78
|
+
if (fields.length === 0) {
|
|
79
|
+
throw new Error(`${context} does not expose any top-level properties that can back binding fields.`);
|
|
80
|
+
}
|
|
81
|
+
return fields;
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Load and extract binding fields from a post-meta JSON Schema artifact.
|
|
85
|
+
*
|
|
86
|
+
* @param projectDir Workspace root directory.
|
|
87
|
+
* @param postMeta Post-meta inventory entry that points to the schema file.
|
|
88
|
+
* @returns Promise resolving to the extracted top-level binding fields.
|
|
89
|
+
* @throws If the schema cannot be read or does not expose object properties.
|
|
90
|
+
*/
|
|
91
|
+
export async function loadPostMetaBindingFields(projectDir, postMeta) {
|
|
92
|
+
const schemaPath = path.join(projectDir, postMeta.schemaFile);
|
|
93
|
+
const schema = await readJsonFile(schemaPath, {
|
|
94
|
+
context: `post meta schema for ${postMeta.slug}`,
|
|
95
|
+
});
|
|
96
|
+
return extractPostMetaBindingFields(schema, postMeta.schemaFile);
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Synchronously load and extract binding fields from a post-meta JSON Schema artifact.
|
|
100
|
+
*
|
|
101
|
+
* @param projectDir Workspace root directory.
|
|
102
|
+
* @param postMeta Post-meta inventory entry that points to the schema file.
|
|
103
|
+
* @returns Extracted top-level binding fields.
|
|
104
|
+
* @throws If the schema cannot be read or does not expose object properties.
|
|
105
|
+
*/
|
|
106
|
+
export function loadPostMetaBindingFieldsSync(projectDir, postMeta) {
|
|
107
|
+
const schemaPath = path.join(projectDir, postMeta.schemaFile);
|
|
108
|
+
const schema = readJsonFileSync(schemaPath, {
|
|
109
|
+
context: `post meta schema for ${postMeta.slug}`,
|
|
110
|
+
});
|
|
111
|
+
return extractPostMetaBindingFields(schema, postMeta.schemaFile);
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* Validate and resolve a top-level post-meta binding path.
|
|
115
|
+
*
|
|
116
|
+
* @param fields Binding fields extracted from the post-meta schema.
|
|
117
|
+
* @param postMetaSlug Post-meta contract slug used in diagnostics.
|
|
118
|
+
* @param metaPath User-provided field path to validate.
|
|
119
|
+
* @returns The matching binding field.
|
|
120
|
+
* @throws If the path is empty, nested, or missing from the schema fields.
|
|
121
|
+
*/
|
|
122
|
+
export function assertPostMetaBindingPath(fields, postMetaSlug, metaPath) {
|
|
123
|
+
const trimmed = metaPath.trim();
|
|
124
|
+
if (!trimmed) {
|
|
125
|
+
throw new Error("Post meta binding path must include a value.");
|
|
126
|
+
}
|
|
127
|
+
if (trimmed.includes(".")) {
|
|
128
|
+
throw new Error(`Nested post meta path "${trimmed}" for "${postMetaSlug}" is not supported yet. Use one of the top-level fields: ${fields.map((field) => field.name).join(", ")}.`);
|
|
129
|
+
}
|
|
130
|
+
const field = fields.find((candidate) => candidate.name === trimmed);
|
|
131
|
+
if (!field) {
|
|
132
|
+
throw new Error(`Post meta path "${trimmed}" does not exist in the "${postMetaSlug}" schema. Expected one of: ${fields.map((candidate) => candidate.name).join(", ")}.`);
|
|
133
|
+
}
|
|
134
|
+
return field;
|
|
135
|
+
}
|
|
@@ -9,6 +9,7 @@ import { getStarterManifestFiles, stringifyStarterManifest } from "./starter-man
|
|
|
9
9
|
import { OFFICIAL_WORKSPACE_TEMPLATE_PACKAGE, PROJECT_TOOLS_PACKAGE_ROOT, } from "./template-registry.js";
|
|
10
10
|
import { getScaffoldTemplateVariableGroups } from "./scaffold-template-variable-groups.js";
|
|
11
11
|
import { pathExists } from "./fs-async.js";
|
|
12
|
+
import { readJsonFile, readJsonFileSync } from "./json-utils.js";
|
|
12
13
|
const EPHEMERAL_NODE_MODULES_LINK_TYPE = process.platform === "win32" ? "junction" : "dir";
|
|
13
14
|
/**
|
|
14
15
|
* Ensures the scaffold target directory exists and is empty unless explicitly allowed.
|
|
@@ -32,7 +33,9 @@ function readGeneratedPackageJson(projectDir) {
|
|
|
32
33
|
if (!fs.existsSync(packageJsonPath)) {
|
|
33
34
|
return null;
|
|
34
35
|
}
|
|
35
|
-
return
|
|
36
|
+
return readJsonFileSync(packageJsonPath, {
|
|
37
|
+
context: "generated package manifest",
|
|
38
|
+
});
|
|
36
39
|
}
|
|
37
40
|
/**
|
|
38
41
|
* Format the actionable error message used when a scaffold target directory
|
|
@@ -125,7 +128,9 @@ export function isOfficialWorkspaceProject(projectDir) {
|
|
|
125
128
|
*/
|
|
126
129
|
export async function applyWorkspaceMigrationCapability(projectDir, packageManager) {
|
|
127
130
|
const packageJsonPath = path.join(projectDir, "package.json");
|
|
128
|
-
const packageJson =
|
|
131
|
+
const packageJson = await readJsonFile(packageJsonPath, {
|
|
132
|
+
context: "workspace package manifest",
|
|
133
|
+
});
|
|
129
134
|
const wpTypiaPackageVersion = getPackageVersions().wpTypiaPackageVersion;
|
|
130
135
|
const canonicalCliSpecifier = wpTypiaPackageVersion === "^0.0.0"
|
|
131
136
|
? "wp-typia"
|